unity-open-mcp 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capabilities/cost-hints.js +73 -0
- package/dist/capabilities/cost-hints.js.map +1 -1
- package/dist/capabilities/intent-groups.js +76 -4
- package/dist/capabilities/intent-groups.js.map +1 -1
- package/dist/capabilities/lifecycle.js +9 -0
- package/dist/capabilities/lifecycle.js.map +1 -1
- package/dist/capabilities/tool-groups.js +75 -17
- package/dist/capabilities/tool-groups.js.map +1 -1
- package/dist/compressible-router.d.ts +4 -1
- package/dist/compressible-router.js +27 -11
- package/dist/compressible-router.js.map +1 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +2 -2
- package/dist/event-stream.d.ts +10 -0
- package/dist/event-stream.js +29 -0
- package/dist/event-stream.js.map +1 -1
- package/dist/game-view-settle.d.ts +18 -0
- package/dist/game-view-settle.js +81 -0
- package/dist/game-view-settle.js.map +1 -0
- package/dist/jobs/adapters.d.ts +59 -0
- package/dist/jobs/adapters.js +226 -0
- package/dist/jobs/adapters.js.map +1 -1
- package/dist/live-client.d.ts +17 -60
- package/dist/live-client.js +52 -87
- package/dist/live-client.js.map +1 -1
- package/dist/local-tools.js +3 -0
- package/dist/local-tools.js.map +1 -1
- package/dist/offline/api.d.ts +2 -0
- package/dist/offline/api.js +4 -2
- package/dist/offline/api.js.map +1 -1
- package/dist/offline/model.js +1 -1
- package/dist/offline/model.js.map +1 -1
- package/dist/offline/parse.d.ts +1 -1
- package/dist/offline/parse.js +20 -8
- package/dist/offline/parse.js.map +1 -1
- package/dist/playmode-routes.d.ts +17 -0
- package/dist/playmode-routes.js +50 -0
- package/dist/playmode-routes.js.map +1 -0
- package/dist/playtest/png.d.ts +27 -0
- package/dist/playtest/png.js +248 -0
- package/dist/playtest/png.js.map +1 -0
- package/dist/playtest/recorder.d.ts +203 -0
- package/dist/playtest/recorder.js +945 -0
- package/dist/playtest/recorder.js.map +1 -0
- package/dist/playtest/redact.d.ts +16 -0
- package/dist/playtest/redact.js +124 -0
- package/dist/playtest/redact.js.map +1 -0
- package/dist/playtest/report.d.ts +73 -0
- package/dist/playtest/report.js +477 -0
- package/dist/playtest/report.js.map +1 -0
- package/dist/playtest/session-store.d.ts +78 -0
- package/dist/playtest/session-store.js +224 -0
- package/dist/playtest/session-store.js.map +1 -0
- package/dist/skill/SKILL.md +22 -3
- package/dist/skill/references/senses-and-tests.md +3 -0
- package/dist/skill/references/yaml-and-offline-work.md +1 -1
- package/dist/tool-router.d.ts +44 -0
- package/dist/tool-router.js +249 -7
- package/dist/tool-router.js.map +1 -1
- package/dist/tools/batch-execute.js +5 -1
- package/dist/tools/batch-execute.js.map +1 -1
- package/dist/tools/capture-inline.js +30 -12
- package/dist/tools/capture-inline.js.map +1 -1
- package/dist/tools/capture-series.d.ts +46 -0
- package/dist/tools/capture-series.js +116 -0
- package/dist/tools/capture-series.js.map +1 -0
- package/dist/tools/game-view.d.ts +44 -0
- package/dist/tools/game-view.js +103 -0
- package/dist/tools/game-view.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +34 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inputsim-gesture.d.ts +46 -0
- package/dist/tools/inputsim-gesture.js +123 -0
- package/dist/tools/inputsim-gesture.js.map +1 -0
- package/dist/tools/inputsim-pointer.js +35 -2
- package/dist/tools/inputsim-pointer.js.map +1 -1
- package/dist/tools/inputsim-touch.js +42 -22
- package/dist/tools/inputsim-touch.js.map +1 -1
- package/dist/tools/jobs.js +1 -1
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/player-state.d.ts +42 -0
- package/dist/tools/player-state.js +142 -0
- package/dist/tools/player-state.js.map +1 -0
- package/dist/tools/playtest-conditions.d.ts +334 -0
- package/dist/tools/playtest-conditions.js +99 -0
- package/dist/tools/playtest-conditions.js.map +1 -0
- package/dist/tools/playtest-session.d.ts +45 -0
- package/dist/tools/playtest-session.js +105 -0
- package/dist/tools/playtest-session.js.map +1 -0
- package/dist/tools/playtest-wait.d.ts +46 -0
- package/dist/tools/playtest-wait.js +86 -0
- package/dist/tools/playtest-wait.js.map +1 -0
- package/dist/tools/screenshot.js +31 -8
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/text-target.d.ts +22 -0
- package/dist/tools/text-target.js +39 -0
- package/dist/tools/text-target.js.map +1 -0
- package/dist/tools/ui-snapshot.d.ts +41 -0
- package/dist/tools/ui-snapshot.js +87 -0
- package/dist/tools/ui-snapshot.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Playtest session recorder — on-disk layout.
|
|
2
|
+
//
|
|
3
|
+
// A session is a deliberate on-disk record (not a cache): the review artifact
|
|
4
|
+
// a human reads after an agent's playtest. It lives in the same platform
|
|
5
|
+
// app-data root as the player-state snapshots, outside the project:
|
|
6
|
+
//
|
|
7
|
+
// <app data>/unity-open-mcp/playtest-sessions/<company>/<product>/<session>/
|
|
8
|
+
// session.json metadata (owner, device, snapshot, state, report path)
|
|
9
|
+
// log.jsonl one JSON line per event, appended as it happens
|
|
10
|
+
// report/
|
|
11
|
+
// frames/ full-size captures (linked or copied when the call ends)
|
|
12
|
+
// thumbs/ downscaled previews (written by `end`)
|
|
13
|
+
// report.md written by `end`
|
|
14
|
+
//
|
|
15
|
+
// `<app data>` mirrors the bridge's player-state root: ~/Library/Application
|
|
16
|
+
// Support on macOS, %LOCALAPPDATA% on Windows, $XDG_DATA_HOME or ~/.local/share
|
|
17
|
+
// elsewhere. Company / product are the project's Player Settings, made into one
|
|
18
|
+
// path segment each with the same rule as the bridge.
|
|
19
|
+
//
|
|
20
|
+
// Every write is synchronous and small: a log line is appended with one
|
|
21
|
+
// appendFileSync (open-write-close) the moment the event happens, so the log
|
|
22
|
+
// survives a domain reload, an Editor crash and an MCP server restart; the
|
|
23
|
+
// metadata file is replaced through a temp file + rename. A process that adopts
|
|
24
|
+
// a session first ends a torn last line, so its own entries stay parseable.
|
|
25
|
+
import { appendFileSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readdirSync, readFileSync, readSync, renameSync, writeFileSync } from "node:fs";
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
export const SESSION_FORMAT = 1;
|
|
29
|
+
export const SESSION_FILE = "session.json";
|
|
30
|
+
export const LOG_FILE = "log.jsonl";
|
|
31
|
+
export const REPORT_DIR = "report";
|
|
32
|
+
export const FRAMES_DIR = "frames";
|
|
33
|
+
export const THUMBS_DIR = "thumbs";
|
|
34
|
+
export const REPORT_FILE = "report.md";
|
|
35
|
+
/** The platform app-data folder (same rule as the bridge's player-state store). */
|
|
36
|
+
export function appDataRoot(platform = process.platform, env = process.env, home = env.HOME ?? homedir()) {
|
|
37
|
+
let appData;
|
|
38
|
+
if (platform === "darwin")
|
|
39
|
+
appData = join(home, "Library", "Application Support");
|
|
40
|
+
else if (platform === "win32")
|
|
41
|
+
appData = env.LOCALAPPDATA && env.LOCALAPPDATA.length ? env.LOCALAPPDATA : join(home, "AppData", "Local");
|
|
42
|
+
else {
|
|
43
|
+
const xdg = env.XDG_DATA_HOME;
|
|
44
|
+
appData = xdg && xdg.startsWith("/") ? xdg : join(home, ".local", "share");
|
|
45
|
+
}
|
|
46
|
+
return join(appData, "unity-open-mcp");
|
|
47
|
+
}
|
|
48
|
+
export function sessionsRoot(platform, env, home) {
|
|
49
|
+
return join(appDataRoot(platform, env, home), "playtest-sessions");
|
|
50
|
+
}
|
|
51
|
+
/** A company / product name as one path segment (the bridge's SafeSegment rule). */
|
|
52
|
+
export function safeSegment(value) {
|
|
53
|
+
if (!value)
|
|
54
|
+
return "_";
|
|
55
|
+
let out = "";
|
|
56
|
+
for (const c of value)
|
|
57
|
+
out += /[\\/:*?"<>|]/.test(c) || c.charCodeAt(0) < 32 ? "_" : c;
|
|
58
|
+
out = out.trim().replace(/\.+$/, "");
|
|
59
|
+
return out.length === 0 || out === "." || out === ".." ? "_" : out;
|
|
60
|
+
}
|
|
61
|
+
/** A session id: UTC stamp + a slug of the name + 4 hex, safe as a folder name. */
|
|
62
|
+
export function sessionId(name, now, random) {
|
|
63
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\..*$/, "").replace("T", "-");
|
|
64
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32) || "playtest";
|
|
65
|
+
return `${stamp}-${slug}-${random}`;
|
|
66
|
+
}
|
|
67
|
+
export const SESSION_ID_PATTERN = /^[0-9]{8}-[0-9]{6}-[a-z0-9-]{1,40}$/;
|
|
68
|
+
/**
|
|
69
|
+
* Company / product from ProjectSettings/ProjectSettings.asset — the fallback
|
|
70
|
+
* when the Editor cannot be asked (a restart with the Editor closed). Plain
|
|
71
|
+
* and quoted YAML scalars only; anything else falls back to Unity's defaults.
|
|
72
|
+
*/
|
|
73
|
+
export function readProjectIdentity(projectPath) {
|
|
74
|
+
let text;
|
|
75
|
+
try {
|
|
76
|
+
text = readFileSync(join(projectPath, "ProjectSettings", "ProjectSettings.asset"), "utf8");
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const scalar = (key) => {
|
|
82
|
+
const match = new RegExp(`^\\s*${key}:[ \\t]*(.*)$`, "m").exec(text);
|
|
83
|
+
if (!match)
|
|
84
|
+
return null;
|
|
85
|
+
const raw = match[1].trim();
|
|
86
|
+
if (raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2)
|
|
87
|
+
return raw.slice(1, -1).replace(/''/g, "'");
|
|
88
|
+
if (raw.startsWith("\"") && raw.endsWith("\"") && raw.length >= 2) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(raw);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return raw.slice(1, -1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return raw;
|
|
97
|
+
};
|
|
98
|
+
const company = scalar("companyName");
|
|
99
|
+
const product = scalar("productName");
|
|
100
|
+
if (company === null || product === null)
|
|
101
|
+
return null;
|
|
102
|
+
return { company, product };
|
|
103
|
+
}
|
|
104
|
+
export class SessionStore {
|
|
105
|
+
root;
|
|
106
|
+
constructor(root) {
|
|
107
|
+
this.root = root;
|
|
108
|
+
}
|
|
109
|
+
productDir(company, product) {
|
|
110
|
+
return join(this.root, safeSegment(company), safeSegment(product));
|
|
111
|
+
}
|
|
112
|
+
dir(meta) {
|
|
113
|
+
return join(this.productDir(meta.company, meta.product), meta.id);
|
|
114
|
+
}
|
|
115
|
+
create(meta) {
|
|
116
|
+
const dir = this.dir(meta);
|
|
117
|
+
mkdirSync(join(dir, REPORT_DIR, FRAMES_DIR), { recursive: true });
|
|
118
|
+
this.writeMeta(meta);
|
|
119
|
+
writeFileSync(join(dir, LOG_FILE), "", { flag: "a" });
|
|
120
|
+
return dir;
|
|
121
|
+
}
|
|
122
|
+
writeMeta(meta) {
|
|
123
|
+
const dir = this.dir(meta);
|
|
124
|
+
const target = join(dir, SESSION_FILE);
|
|
125
|
+
const temp = `${target}.${process.pid}.tmp`;
|
|
126
|
+
writeFileSync(temp, JSON.stringify(meta, null, 2));
|
|
127
|
+
renameSync(temp, target);
|
|
128
|
+
}
|
|
129
|
+
readMeta(dir) {
|
|
130
|
+
try {
|
|
131
|
+
const meta = JSON.parse(readFileSync(join(dir, SESSION_FILE), "utf8"));
|
|
132
|
+
return meta && meta.format === SESSION_FORMAT && typeof meta.id === "string" ? meta : null;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
append(meta, entry) {
|
|
139
|
+
appendFileSync(join(this.dir(meta), LOG_FILE), JSON.stringify(entry) + "\n");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Every parseable line of a session log; a torn line (a crash mid-write) is
|
|
143
|
+
* skipped. Read in chunks, so a log larger than the longest JS string still
|
|
144
|
+
* yields its entries.
|
|
145
|
+
*/
|
|
146
|
+
readLog(dir) {
|
|
147
|
+
let fd;
|
|
148
|
+
try {
|
|
149
|
+
fd = openSync(join(dir, LOG_FILE), "r");
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
const out = [];
|
|
155
|
+
const parse = (line) => {
|
|
156
|
+
const text = line.toString("utf8");
|
|
157
|
+
if (!text.trim())
|
|
158
|
+
return;
|
|
159
|
+
try {
|
|
160
|
+
out.push(JSON.parse(text));
|
|
161
|
+
}
|
|
162
|
+
catch { /* torn line */ }
|
|
163
|
+
};
|
|
164
|
+
try {
|
|
165
|
+
const chunk = Buffer.alloc(1 << 20);
|
|
166
|
+
let rest = Buffer.alloc(0);
|
|
167
|
+
for (let read = readSync(fd, chunk, 0, chunk.length, null); read > 0; read = readSync(fd, chunk, 0, chunk.length, null)) {
|
|
168
|
+
const data = rest.length ? Buffer.concat([rest, chunk.subarray(0, read)]) : chunk.subarray(0, read);
|
|
169
|
+
let start = 0;
|
|
170
|
+
for (let newline = data.indexOf(10); newline >= 0; newline = data.indexOf(10, start)) {
|
|
171
|
+
parse(data.subarray(start, newline));
|
|
172
|
+
start = newline + 1;
|
|
173
|
+
}
|
|
174
|
+
// Copied: `chunk` is reused by the next read.
|
|
175
|
+
rest = Buffer.from(data.subarray(start));
|
|
176
|
+
}
|
|
177
|
+
if (rest.length)
|
|
178
|
+
parse(rest);
|
|
179
|
+
}
|
|
180
|
+
catch { /* an unreadable tail: keep the entries read so far */ }
|
|
181
|
+
finally {
|
|
182
|
+
closeSync(fd);
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
/** End a torn last line, so the next appended entry starts a line of its own. */
|
|
187
|
+
terminateLog(dir) {
|
|
188
|
+
const file = join(dir, LOG_FILE);
|
|
189
|
+
let torn = false;
|
|
190
|
+
try {
|
|
191
|
+
const fd = openSync(file, "r");
|
|
192
|
+
try {
|
|
193
|
+
const size = fstatSync(fd).size;
|
|
194
|
+
const last = Buffer.alloc(1);
|
|
195
|
+
torn = size > 0 && readSync(fd, last, 0, 1, size - 1) === 1 && last[0] !== 10;
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
closeSync(fd);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (torn)
|
|
205
|
+
appendFileSync(file, "\n");
|
|
206
|
+
}
|
|
207
|
+
/** Sessions of one product, newest first. */
|
|
208
|
+
list(company, product) {
|
|
209
|
+
const base = this.productDir(company, product);
|
|
210
|
+
if (!existsSync(base))
|
|
211
|
+
return [];
|
|
212
|
+
const out = [];
|
|
213
|
+
for (const name of readdirSync(base)) {
|
|
214
|
+
if (!SESSION_ID_PATTERN.test(name))
|
|
215
|
+
continue;
|
|
216
|
+
const dir = join(base, name);
|
|
217
|
+
const meta = this.readMeta(dir);
|
|
218
|
+
if (meta)
|
|
219
|
+
out.push({ dir, meta });
|
|
220
|
+
}
|
|
221
|
+
return out.sort((a, b) => b.meta.started_at.localeCompare(a.meta.started_at));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/playtest/session-store.ts"],"names":[],"mappings":"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"}
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -13,6 +13,7 @@ Use exact schemas, scoped mutations, and gate evidence to work on the selected U
|
|
|
13
13
|
3. Confirm the target project and live state with `ping` / `bridge_status`; use the instance lock’s port, never a hardcoded port. Offline reads and `read_compile_errors` do not require a live bridge.
|
|
14
14
|
4. Prefer typed tools. Use canonical `asset_path`, `game_object_path`, `component_type`, and `property_path` from the exact schema. Start compact and page reads; expand only the relevant slice.
|
|
15
15
|
5. Scope mutations with nonempty `paths_hint`, then inspect `gate.delta` and `agentNextSteps`. Mutation success does not prove project safety. Validate and test the affected scope.
|
|
16
|
+
6. After a lost response, socket reset, or timeout, inspect post-state before retrying a mutation, even if the bridge restarted. If `delta` returns `validation_incomplete`, resolve `error.rulesFailed` and create a fresh checkpoint. Copy scan `issueId` unchanged into `apply_fix.issue_id`; use `profile: "full"` to retrieve issues.
|
|
16
17
|
|
|
17
18
|
## Non-negotiable safety
|
|
18
19
|
|
|
@@ -20,6 +21,7 @@ Use exact schemas, scoped mutations, and gate evidence to work on the selected U
|
|
|
20
21
|
- Never silently discard dirty scenes or bypass lifecycle guards. Pure `read_only` snippets must not write, refresh, compile, switch scenes, or change play state; this flag is an assertion, not a sandbox.
|
|
21
22
|
- While the Editor is reachable, prefer typed live edits. Never directly edit an open/dirty scene or asset YAML. Explicitly identify necessary file repair as the offline-file route; preserve GUID/fileID links, check the intended scene, then reimport/reserialize and run scoped validation.
|
|
22
23
|
- Run one test request at a time. `run_id` names the started run, not a poll handle. Do not retry an unresolved run or put server-polled tests/recovery tools in a live batch.
|
|
24
|
+
- If a top-level fix reports `rollback_unavailable` because backup failed, repair temporary-directory space or file permissions before retrying; do not bypass the refusal.
|
|
23
25
|
- Batch arguments are validated before step zero. All-read batches need no mutation scope; mixed batches require aggregate scope. A failed mutation with a skipped gate has not passed validation.
|
|
24
26
|
|
|
25
27
|
## Load only the reference needed now
|
|
@@ -30,6 +32,8 @@ Use exact schemas, scoped mutations, and gate evidence to work on the selected U
|
|
|
30
32
|
- Before batching, interpreting a failed gate, or applying fixes: [Batch and gates](references/batch-and-gates.md).
|
|
31
33
|
- Before file-level repair, YAML edits, or offline asset investigation: [YAML and offline work](references/yaml-and-offline-work.md).
|
|
32
34
|
- Before screenshots, event capture, or test runs: [Senses and tests](references/senses-and-tests.md).
|
|
35
|
+
- Before driving gameplay input (taps, drags, multi-second gestures — legacy Input Manager or Input System): activate `input-simulation` and follow its domain skill; read the reported `backend` array and refusals instead of reflecting into game input code.
|
|
36
|
+
- To playtest the game (read the screen, tap by label, wait, capture, isolate the save, record a report for a human): follow [Playtest](#playtest) below.
|
|
33
37
|
|
|
34
38
|
### Project-owned commands
|
|
35
39
|
|
|
@@ -50,9 +54,12 @@ commands in `batch_execute`.
|
|
|
50
54
|
|
|
51
55
|
Use the always-visible `unity_open_mcp_jobs` for explicitly supported asynchronous
|
|
52
56
|
operations. `start` requires `tool_or_command`, adapter `args`, and an
|
|
53
|
-
`idempotency_key
|
|
54
|
-
filters, no caller `run_id`, cancellation
|
|
55
|
-
commands (`args: {args: {...}, gate,
|
|
57
|
+
`idempotency_key` (optional for read-only targets). Supported targets are
|
|
58
|
+
`unity_senses_run_tests` (ordinary test filters, no caller `run_id`, cancellation
|
|
59
|
+
unsupported), available async project commands (`args: {args: {...}, gate,
|
|
60
|
+
paths_hint}`), and play-mode operations (`unity_open_mcp_inputsim_gesture`,
|
|
61
|
+
`unity_open_mcp_playtest_wait`, `unity_senses_capture_series`; the tool's own args;
|
|
62
|
+
cancellable — cancel releases held input and restores a wait's time scale). Read terminal project-job gate
|
|
56
63
|
results even on failure/cancellation. An unsupported target must not be retried as
|
|
57
64
|
an arbitrary background tool.
|
|
58
65
|
|
|
@@ -66,3 +73,15 @@ before retrying. Records and keys expire 30 minutes after completion and are los
|
|
|
66
73
|
on server restart. `job_busy` means a project job owns the Editor: mutations, Editor-state
|
|
67
74
|
writes, test starts and every `execute_csharp` (even `read_only: true`) are refused.
|
|
68
75
|
Wait on `error.job.jobId` via jobs (owner only), use typed reads meanwhile, retry once; never loop-retry. See [Routing and lifecycle](references/routing-and-lifecycle.md).
|
|
76
|
+
|
|
77
|
+
## Playtest
|
|
78
|
+
|
|
79
|
+
Activate `playtest` with `input-simulation` (`manage_tools` `activate_for`, intent "playtest …").
|
|
80
|
+
|
|
81
|
+
1. **Prepare.** Device first: `game_view get` (note what to restore), then `set_device` (Simulator device + orientation; read `screen.safe_area` / `insets`) or `set_resolution`; continue only on `settled: true`. Isolate the state: `project_commands(action="list", query="playtest")` and invoke the project's `playtest.begin` when it exists; otherwise start the session with `snapshot: true`. Then `playtest_session {"action":"start","name":"…","device":"…"}` — from now your input / wait / capture / project-command calls are recorded with their frames and console errors (one open session per agent: a second start is `session_already_open`).
|
|
82
|
+
2. **Boot** with `editor_set_state play` (then `editor_status` until playing) and the project's `playtest.boot` command. Never reflect into launchers, singletons or save systems.
|
|
83
|
+
3. **Loop.** `ui_snapshot` (compact, paged) → act like a player: `target_text` on `inputsim_pointer` / `inputsim_touch` (resolve `ambiguous_target` with `index` / `under`, never by guessing names) or `inputsim_gesture` → one `playtest_wait` on a condition (`log` regex, `ui` query — `matches:false` for "gone", `object`, the project's read-only `project_command`, `frames`, `ms`, `all`/`any`) with a `timeout_ms`, never a polling loop of snapshots, steps or `execute_csharp`; read `fired`, on `timed_out` read `condition.last` → look: `unity_senses_capture_series` for anything animated (`start_on`, `contact_sheet: true`, `metric: "diff"` in a `metric_region`), a composed screenshot (no width/height; quote its `capturedAt`) for a static screen → `playtest_session {"action":"mark","label":"…","verdict":"ok|bug|suspicious","note":"…"}`.
|
|
84
|
+
4. **Findings.** Every console exception or error is a finding of the step that caused it: read `since_last_mark.errors` on each mark; `fail_on: ["exception"]` stops a wait at the first one with its stack.
|
|
85
|
+
5. **Finish.** `playtest_session {"action":"end"}` → report path + summary. Stop play mode; invoke `playtest.end`, or `player_state restore` (dry run: read `prefs.removed` / `files.removed`; then `confirm: true` with its `plan`, check `verified`) and `delete` the session's snapshot; put the Game view back (`set_resolution` with the `size_label` from `get`). Hand the report path and the bug / suspicious steps to the human.
|
|
86
|
+
|
|
87
|
+
Traps: one frame lies about animation — use a series (a tap and what follows: `inputsim_gesture` `capture_at_ms`). Captures name their device: set it before capturing; a hidden Game view tab does not render. Never mutate project assets (scenes, prefabs, writing snippets) during a playtest. Restore overwrites PlayerPrefs and deletes `persistentDataPath` files not in the snapshot; never restore a snapshot you did not take for this session. Waits / series over 40 s return a `job_id` → `jobs wait`. After an MCP server restart, `playtest_session status` lists your session under `orphaned`: `end` (or `resume`) with `session: "<id>"`.
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
Exit codes: `0` = all tests passed, `2` = test failures present (NOT a process error — do not treat 2 as a crash), `3` = test runner setup failure. Known **batchmode-only** red tests that pass in the GUI editor — don't try to "fix" these, validate them in the GUI editor instead: in-process HTTP-server lifecycle (socket-disposed), GPU/SceneView (no graphic device under `-nographics`), Unity-6's removed `LogEntries.GetEntries(List<LogEntry>)` internal API (console/capture), MSBuild/Roslyn `Microsoft.Build.Utilities.Core` host assembly load, and TimeManager SerializedProperty float access.
|
|
15
15
|
- `**unity_senses_read_console`** — console entries via reflection. Filter `type: "error"` to confirm clean compile. `detail: "summary"` for messages only (saves tokens); `detail: "verbose"` includes Unity-internal frames.
|
|
16
16
|
- `**unity_senses_screenshot**` — Scene / Game / **composed** / isolated 2×2 composite of one GameObject. `view: "game"` renders `Camera.main` **only** — Screen-Space Overlay UI and multi-camera compositions are NOT in the frame; use `view: "composed"` to verify UI edits (the full player frame: all cameras by depth + Overlay canvases).
|
|
17
|
+
- **Composed size.** Omit `width`/`height` to get the game's native resolution (the Game view size or the Device Simulator screen; no Editor chrome, no device bezel). A `width`/`height` of another aspect is padded (letterbox), never stretched — read `scaled_from` and `content_rect`. `overlay: "safe_area"` tints the unsafe band and outlines the safe area. Every game/composed response names what it was taken at in `capturedAt` (mode, device or size entry, orientation, screen) — quote it when reporting a frame.
|
|
18
|
+
- **Device first, then capture.** Put the game on the target resolution/device with `unity_open_mcp_game_view` (`playtest` group) and wait for `settled: true` before capturing; a frame taken while `not_settled` shows the old size.
|
|
19
|
+
- `**unity_senses_capture_series**` (`playtest` group, play mode) — the composed frame at scheduled frames inside the player loop: `at_ms` / `every_ms`+`duration_ms` / `at_frames` (≤ 120), relative to the call or a `start_on` condition (playtest_wait shapes). Each entry names the `frame` / `play_time` its image shows; `unchanged` = same pixels as the previous capture (static screen or hidden Game view). `region` crop, `scale`, `metric` (`luminance` / `diff` inside `metric_region`) and `contact_sheet: true` (one tiled PNG labelled `#i F<frame> +<ms>MS`). Use it for anything animated — two one-shot frames cannot tell a pulse from a change. Long series return a `job_id`.
|
|
17
20
|
- `**unity_senses_screenshot_camera**` — render from an arbitrary world-space pose (position + rotation + fov) without moving the scene/game camera; transient camera, scene camera untouched.
|
|
18
21
|
- `**unity_senses_capture_inline**` — same targets as `screenshot` but returns the PNG as an inline base64 image (no temp file) for agents that don't read the filesystem.
|
|
19
22
|
- `**unity_senses_visual_compare**` — visual regression: capture a named reference snapshot (`action: save`) from scene / game / isolated view, then `action: compare` a later capture against it to check whether an edit visually broke anything. Returns `pixelDiffPercent`, `mismatchedPixels`, `perceptualDistance` (8×8 average-hash Hamming distance — tolerates minor anti-aliasing drift), and `match` (true when `pixelDiffPercent <= sensitivity * 100`; default `sensitivity: 0.01` = 1%). On a non-zero diff, returns an inline diff image (mismatched pixels highlighted red over the current frame). References persist under `~/.unity-open-mcp/screenshots/references/`; `action: list` / `delete` manage them. The `compare` resamples the current capture to the reference dimensions if they differ, so the diff is resolution-stable.
|
|
@@ -17,7 +17,7 @@ Use `**search_assets`** to locate prefabs/components/GUIDs; each result tags *wh
|
|
|
17
17
|
Raw Unity data is large. Prefer the cheap, structured reads before reaching for verbose output:
|
|
18
18
|
|
|
19
19
|
- **`read_asset`** returns a folded `tree` + `cmp` table + counts, not raw YAML. Drill into a subtree with `component` / `path` + `field_limit` instead of re-reading the whole asset; the parsed model is session-cached (`_cache: "hit"`). `field_limit: 0` (default) returns field names only — bump it only for a `component` drill-down where you need values.
|
|
20
|
-
- **`manage_tools(action="list_groups")`** — sessions start with two default-on groups (`core` + `gate-and-verify`); activate only the other group you need so the full **
|
|
20
|
+
- **`manage_tools(action="list_groups")`** — sessions start with two default-on groups (`core` + `gate-and-verify`); activate only the other group you need so the full **270+ tool** surface stays out of the prompt. Prefer `action="activate_for"` with a free-text `intent` (or `action="suggest"` to preview) over hand-picking ids — it brings the right groups online in one call.
|
|
21
21
|
- **`read_console`** with `detail: "summary"` returns messages only; reserve `detail: "verbose"` for when you need Unity-internal stack frames.
|
|
22
22
|
- **`search_assets`** tags *why* each result matched, so you skip broad reads and go straight to the right drill-down.
|
|
23
23
|
- **Output profiles + paging.** The heavy tools (`read_asset`, `search_assets`, `scene_get_data`, `find_references`, `validate_edit`, `scan_paths`, `component_get`) take `profile: compact|balanced|full` (`compact` is the default — the cheap folded/counts shape). Raise to `balanced`/`full` to expand. Page large results with `page_size` + the `pagination.next_cursor` token (omit `page_size` to get the whole payload at once). For `component_get`, start compact then use `property_path` or paging to expand one subtree. For `find_references` / `validate_edit` / `scan_paths`, the compact default returns counts/groupings only — pass `profile: "balanced"` to get the per-asset / per-issue lists those tools previously returned by default. (The legacy `detail` / `max_results` / `max_nodes` caps remain as aliases.)
|
package/dist/tool-router.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
|
5
5
|
import type { Router } from "./router.js";
|
|
6
6
|
import { LiveClient } from "./live-client.js";
|
|
7
7
|
import type { BridgeEventStream } from "./event-stream.js";
|
|
8
|
+
import { PlaytestRecorder } from "./playtest/recorder.js";
|
|
8
9
|
export declare function resolveRouteLogEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
9
10
|
export declare const ROUTE_LOG_ENABLED: boolean;
|
|
10
11
|
/** Log a happy-path route trace iff the opt-in env flag is set. No-op by
|
|
@@ -74,6 +75,26 @@ export declare class ToolRouter implements Router {
|
|
|
74
75
|
get jobs(): JobManager;
|
|
75
76
|
closeJobs(): void;
|
|
76
77
|
routeJobs(args: Record<string, unknown>, identity?: RequestIdentity): Promise<CallToolResult>;
|
|
78
|
+
/**
|
|
79
|
+
* Play-mode operations (`inputsim_gesture`, `playtest_wait`,
|
|
80
|
+
* `capture_series`): the bridge starts the operation on its shared play-mode
|
|
81
|
+
* scheduler and answers at once; this route polls POST /playmode-operations
|
|
82
|
+
* until the terminal result, inside the host-safe window. An operation whose
|
|
83
|
+
* worst case (`playmodeEstimateMs`: last keyframe / capture, the wait's
|
|
84
|
+
* timeout, a series' start_on wait + schedule + encoding) exceeds the inline
|
|
85
|
+
* budget is started as a `jobs` play-mode operation instead and the call
|
|
86
|
+
* returns that job id. One that outlives the inline poll anyway (gameplay
|
|
87
|
+
* time, a paused Editor) is cancelled — which releases held input and
|
|
88
|
+
* restores a wait's time scale — and the error points at the jobs route.
|
|
89
|
+
*/
|
|
90
|
+
private routePlaymodeOperation;
|
|
91
|
+
/**
|
|
92
|
+
* `game_view`: set_resolution / set_device in play mode return once the game
|
|
93
|
+
* reports the new size — the bridge starts a settle watch on its play-mode
|
|
94
|
+
* scheduler and this route polls it (then re-reads `get`), or answers
|
|
95
|
+
* `not_settled`. get / list_devices and edit-mode changes pass through.
|
|
96
|
+
*/
|
|
97
|
+
private routeGameView;
|
|
77
98
|
private readonly modelCache;
|
|
78
99
|
/**
|
|
79
100
|
* M28-refactoring Plan 3 (T3.2) — named-tool dispatch map. Replaces the
|
|
@@ -120,7 +141,30 @@ export declare class ToolRouter implements Router {
|
|
|
120
141
|
* request's agent id.
|
|
121
142
|
*/
|
|
122
143
|
routeOverride(toolName: string, args: Record<string, unknown>, overrideLive: LiveClient, identity: RequestIdentity): Promise<CallToolResult>;
|
|
144
|
+
/**
|
|
145
|
+
* Every entry point funnels through here (direct, manage_tools invoke, port
|
|
146
|
+
* override). While the caller's agent owner has an open playtest session, a
|
|
147
|
+
* recorded call (input / wait / capture / project command / play-mode and
|
|
148
|
+
* Game view changes) is logged around its dispatch; everything else — and
|
|
149
|
+
* every call when no session is open — goes straight through. The recorder
|
|
150
|
+
* never changes a result and never fails a call.
|
|
151
|
+
*/
|
|
123
152
|
private routeCore;
|
|
153
|
+
/** The owner a request files sessions (and jobs) under. */
|
|
154
|
+
private ownerOf;
|
|
155
|
+
private playtestRecorder?;
|
|
156
|
+
/** The session recorder, created on first use and fed by the bridge event stream. */
|
|
157
|
+
get playtest(): PlaytestRecorder;
|
|
158
|
+
/** Install a recorder (tests use a temporary root). */
|
|
159
|
+
usePlaytestRecorder(recorder: PlaytestRecorder): void;
|
|
160
|
+
/**
|
|
161
|
+
* `unity_open_mcp_playtest_session` — local route. Bridge reads the session
|
|
162
|
+
* needs (company / product, the Game view state, a player-state snapshot or
|
|
163
|
+
* diff) go through the normal route as the same owner, and only when the
|
|
164
|
+
* bridge answers /ping; otherwise the recorder falls back to disk.
|
|
165
|
+
*/
|
|
166
|
+
private routePlaytestSession;
|
|
167
|
+
private routeDispatch;
|
|
124
168
|
private routeValidated;
|
|
125
169
|
private routeUnchecked;
|
|
126
170
|
private batchPingResult;
|