gdharness 1.2.26 → 1.2.28

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/build/cli.js CHANGED
@@ -8198,24 +8198,106 @@ var init_engine_run = __esm(() => {
8198
8198
  init_game_log();
8199
8199
  });
8200
8200
 
8201
+ // src/library-copies.ts
8202
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
8203
+ import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute2, join as join5 } from "node:path";
8204
+ function freeLibraryCopies(projectPath, platform = process.platform) {
8205
+ const setAside = [];
8206
+ const swept = [];
8207
+ if (platform !== "win32") {
8208
+ return { setAside, swept };
8209
+ }
8210
+ for (const library of windowsLibraries(projectPath)) {
8211
+ const folder = dirname4(library);
8212
+ const copy = `~${basename3(library)}`;
8213
+ if (existsSync5(join5(folder, copy))) {
8214
+ const aside = join5(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
8215
+ try {
8216
+ renameSync(join5(folder, copy), aside);
8217
+ setAside.push(aside);
8218
+ } catch {}
8219
+ }
8220
+ let names;
8221
+ try {
8222
+ names = readdirSync3(folder);
8223
+ } catch {
8224
+ continue;
8225
+ }
8226
+ for (const name of names) {
8227
+ if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
8228
+ try {
8229
+ unlinkSync(join5(folder, name));
8230
+ swept.push(join5(folder, name));
8231
+ } catch {}
8232
+ }
8233
+ }
8234
+ }
8235
+ return { setAside, swept };
8236
+ }
8237
+ function windowsLibraries(projectPath) {
8238
+ const listed = readOrNull(join5(projectPath, ".godot", "extension_list.cfg"));
8239
+ if (listed === null) {
8240
+ return [];
8241
+ }
8242
+ const libraries = new Set;
8243
+ for (const line of listed.split(/\r?\n/)) {
8244
+ const manifest = resolveResource(projectPath, projectPath, line.trim());
8245
+ const text = manifest === null ? null : readOrNull(manifest);
8246
+ if (manifest === null || text === null) {
8247
+ continue;
8248
+ }
8249
+ let inLibraries = false;
8250
+ for (const row of text.split(/\r?\n/)) {
8251
+ const section = /^\s*\[([^\]]+)\]/.exec(row)?.[1];
8252
+ if (section !== undefined) {
8253
+ inLibraries = section.trim() === "libraries";
8254
+ continue;
8255
+ }
8256
+ const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
8257
+ const library = named === undefined ? null : resolveResource(projectPath, dirname4(manifest), named);
8258
+ if (library !== null) {
8259
+ libraries.add(library);
8260
+ }
8261
+ }
8262
+ }
8263
+ return [...libraries];
8264
+ }
8265
+ function resolveResource(projectPath, relativeTo, path) {
8266
+ if (path === "") {
8267
+ return null;
8268
+ }
8269
+ if (path.startsWith("res://")) {
8270
+ return join5(projectPath, path.slice("res://".length));
8271
+ }
8272
+ return isAbsolute2(path) ? path : join5(relativeTo, path);
8273
+ }
8274
+ function readOrNull(path) {
8275
+ try {
8276
+ return readFileSync3(path, "utf8");
8277
+ } catch {
8278
+ return null;
8279
+ }
8280
+ }
8281
+ var init_library_copies = () => {};
8282
+
8201
8283
  // src/reimport.ts
8202
8284
  import { createHash } from "node:crypto";
8203
- import { existsSync as existsSync5, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync3, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
8204
- import { basename as basename3, join as join5 } from "node:path";
8285
+ import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync3, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
8286
+ import { basename as basename4, join as join6 } from "node:path";
8205
8287
  function forceNextImport(projectPath, resourcePath) {
8206
8288
  const relative = resourcePath.replace(/^res:\/\//, "");
8207
- const sidecar = join5(projectPath, `${relative}.import`);
8208
- if (!existsSync5(sidecar)) {
8289
+ const sidecar = join6(projectPath, `${relative}.import`);
8290
+ if (!existsSync6(sidecar)) {
8209
8291
  return;
8210
8292
  }
8211
8293
  const { atime, mtimeMs } = statSync3(sidecar);
8212
- writeFileSync3(sidecar, readFileSync3(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
8294
+ writeFileSync3(sidecar, readFileSync4(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
8213
8295
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
8214
8296
  rmSync3(importRecord(projectPath, resourcePath), { force: true });
8215
8297
  }
8216
8298
  function importRecord(projectPath, resourcePath) {
8217
8299
  const digest = createHash("md5").update(resourcePath).digest("hex");
8218
- return join5(projectPath, ".godot", "imported", `${basename3(resourcePath)}-${digest}.md5`);
8300
+ return join6(projectPath, ".godot", "imported", `${basename4(resourcePath)}-${digest}.md5`);
8219
8301
  }
8220
8302
  var init_reimport = () => {};
8221
8303
 
@@ -8235,17 +8317,17 @@ function alive(pid) {
8235
8317
  var init_alive = () => {};
8236
8318
 
8237
8319
  // src/scratch.ts
8238
- import { existsSync as existsSync6, mkdtempSync, readdirSync as readdirSync3, rmSync as rmSync4 } from "node:fs";
8320
+ import { existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
8239
8321
  import { tmpdir as tmpdir2 } from "node:os";
8240
- import { join as join6 } from "node:path";
8322
+ import { join as join7 } from "node:path";
8241
8323
  import process6 from "node:process";
8242
8324
  function scratchDirectory(kind) {
8243
- return mkdtempSync(join6(tmpdir2(), `gdharness-${kind}-${process6.pid}-`));
8325
+ return mkdtempSync(join7(tmpdir2(), `gdharness-${kind}-${process6.pid}-`));
8244
8326
  }
8245
8327
  function sweepAbandonedScratch(directory = tmpdir2()) {
8246
8328
  let entries;
8247
8329
  try {
8248
- entries = readdirSync3(directory);
8330
+ entries = readdirSync4(directory);
8249
8331
  } catch {
8250
8332
  return;
8251
8333
  }
@@ -8256,7 +8338,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
8256
8338
  continue;
8257
8339
  }
8258
8340
  try {
8259
- remove(join6(directory, entry));
8341
+ remove(join7(directory, entry));
8260
8342
  } catch {}
8261
8343
  }
8262
8344
  }
@@ -8294,7 +8376,7 @@ function tryAgain(path, attempt) {
8294
8376
  lastTryOnExit();
8295
8377
  }
8296
8378
  const after = AGAIN_AFTER_MS[attempt];
8297
- if (after === undefined || !existsSync6(path)) {
8379
+ if (after === undefined || !existsSync7(path)) {
8298
8380
  waiting.delete(path);
8299
8381
  if (after === undefined) {
8300
8382
  atExit.add(path);
@@ -8341,8 +8423,8 @@ var init_scratch = __esm(() => {
8341
8423
  });
8342
8424
 
8343
8425
  // src/headless.ts
8344
- import { existsSync as existsSync7, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
8345
- import { dirname as dirname4, join as join7 } from "node:path";
8426
+ import { existsSync as existsSync8, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
8427
+ import { dirname as dirname5, join as join8 } from "node:path";
8346
8428
  function snakeCased(params) {
8347
8429
  const result = emptyRecord();
8348
8430
  for (const [key, value] of Object.entries(params)) {
@@ -8397,7 +8479,7 @@ function refusalsIn(log) {
8397
8479
  }
8398
8480
  function scriptFault(problems, script) {
8399
8481
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
8400
- const directories = [...new Set([spelled(dirname4(script)), spelled(resolvedOr(dirname4(script)))])];
8482
+ const directories = [...new Set([spelled(dirname5(script)), spelled(resolvedOr(dirname5(script)))])];
8401
8483
  return problems.find((entry) => {
8402
8484
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
8403
8485
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
@@ -8422,7 +8504,7 @@ function librariesNotCopied(messages) {
8422
8504
  }
8423
8505
  function extensionRetryNote(libraries, stillNotLoaded = []) {
8424
8506
  const second = stillNotLoaded.length === 0 ? "so the import was run again and loaded the extension, and the messages are from that second run." : `so the import was run again, and it still could not load ${stillNotLoaded.join(" and ")}: this answer is from an import without ${stillNotLoaded.length === 1 ? "it" : "them"}, and its messages say what the engine hit.`;
8425
- return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "can be deleted once the editor has exited.";
8507
+ return `The first import could not load ${libraries.join(" and ")}: on Windows an engine running as the editor ` + "loads a GDExtension from a ~ copy beside the library, and the editor open on this project holds its own " + "copy there, so the first engine started beside it cannot make one. That failed attempt moves the " + `editor's copy aside, ${second} The editor's copy is left beside the library as a ~RF….TMP file, which ` + "the next import, export or editor this server starts on the project deletes once the editor has exited.";
8426
8508
  }
8427
8509
  async function runImport(godotPath, projectPath, options = {}) {
8428
8510
  const once = options.once ?? importOnce;
@@ -8430,6 +8512,7 @@ async function runImport(godotPath, projectPath, options = {}) {
8430
8512
  for (const target of options.forcing ?? []) {
8431
8513
  forceNextImport(projectPath, target);
8432
8514
  }
8515
+ freeLibraryCopies(projectPath);
8433
8516
  return once(godotPath, projectPath);
8434
8517
  };
8435
8518
  const first = await pass();
@@ -8453,7 +8536,7 @@ async function importOnce(godotPath, projectPath) {
8453
8536
  ran = await runEngine(godotPath, [
8454
8537
  "--headless",
8455
8538
  "--log-file",
8456
- join7(logDir, "engine.log"),
8539
+ join8(logDir, "engine.log"),
8457
8540
  "--path",
8458
8541
  projectPath,
8459
8542
  "--import"
@@ -8473,13 +8556,13 @@ async function importOnce(godotPath, projectPath) {
8473
8556
  }
8474
8557
  async function runOperation(engine, operation, params, projectPath) {
8475
8558
  const paramsDir = scratchDirectory("params");
8476
- const paramsFile = join7(paramsDir, `${operation}.json`);
8477
- const answerFile = join7(paramsDir, "answer.json");
8559
+ const paramsFile = join8(paramsDir, `${operation}.json`);
8560
+ const answerFile = join8(paramsDir, "answer.json");
8478
8561
  writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
8479
8562
  const args = [
8480
8563
  "--headless",
8481
8564
  "--log-file",
8482
- join7(paramsDir, "engine.log"),
8565
+ join8(paramsDir, "engine.log"),
8483
8566
  "--path",
8484
8567
  projectPath,
8485
8568
  "--script",
@@ -8549,10 +8632,10 @@ async function runOperation(engine, operation, params, projectPath) {
8549
8632
  };
8550
8633
  }
8551
8634
  function answerIn(file) {
8552
- if (!existsSync7(file)) {
8635
+ if (!existsSync8(file)) {
8553
8636
  return { kind: "none" };
8554
8637
  }
8555
- const text = readFileSync4(file, "utf8");
8638
+ const text = readFileSync5(file, "utf8");
8556
8639
  try {
8557
8640
  const parsed = JSON.parse(text);
8558
8641
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
@@ -8566,14 +8649,15 @@ function answerIn(file) {
8566
8649
  var AFTER_ANSWER_NOTE = "Printed after this operation had written its answer, while the engine ran the project's autoloads and shut down: Godot starts a project's autoloads in every script run and cannot be told not to. An error raised in a project script here is that autoload's own, and none of these changes the answer.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
8567
8650
  var init_headless = __esm(() => {
8568
8651
  init_engine_run();
8652
+ init_library_copies();
8569
8653
  init_reimport();
8570
8654
  init_scratch();
8571
8655
  });
8572
8656
 
8573
8657
  // src/update-check.ts
8574
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
8658
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8575
8659
  import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
8576
- import { join as join8 } from "node:path";
8660
+ import { join as join9 } from "node:path";
8577
8661
  function packageUrl(root) {
8578
8662
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
8579
8663
  }
@@ -8595,28 +8679,28 @@ function cacheDirectory(environment) {
8595
8679
  };
8596
8680
  const home = set("HOME") ?? homedir3();
8597
8681
  if (process.platform === "win32") {
8598
- return join8(set("LOCALAPPDATA") ?? home, "gdharness");
8682
+ return join9(set("LOCALAPPDATA") ?? home, "gdharness");
8599
8683
  }
8600
8684
  if (process.platform === "darwin") {
8601
- return join8(home, "Library", "Caches", "gdharness");
8685
+ return join9(home, "Library", "Caches", "gdharness");
8602
8686
  }
8603
- return join8(set("XDG_CACHE_HOME") ?? join8(home, ".cache"), "gdharness");
8687
+ return join9(set("XDG_CACHE_HOME") ?? join9(home, ".cache"), "gdharness");
8604
8688
  }
8605
8689
  function cacheFile(environment = process.env) {
8606
8690
  try {
8607
8691
  const directory = cacheDirectory(environment);
8608
8692
  mkdirSync3(directory, { recursive: true, mode: 448 });
8609
- return join8(directory, "update-check.json");
8693
+ return join9(directory, "update-check.json");
8610
8694
  } catch {
8611
- return join8(tmpdir3(), "gdharness-update-check.json");
8695
+ return join9(tmpdir3(), "gdharness-update-check.json");
8612
8696
  }
8613
8697
  }
8614
8698
  function readCache(path) {
8615
8699
  try {
8616
- if (!existsSync8(path)) {
8700
+ if (!existsSync9(path)) {
8617
8701
  return null;
8618
8702
  }
8619
- const parsed = JSON.parse(readFileSync5(path, "utf8"));
8703
+ const parsed = JSON.parse(readFileSync6(path, "utf8"));
8620
8704
  if (typeof parsed !== "object" || parsed === null) {
8621
8705
  return null;
8622
8706
  }
@@ -8764,7 +8848,7 @@ var init_update_check = __esm(() => {
8764
8848
  });
8765
8849
 
8766
8850
  // src/server-version.ts
8767
- import { readFileSync as readFileSync6 } from "node:fs";
8851
+ import { readFileSync as readFileSync7 } from "node:fs";
8768
8852
  function editorIsStale(addonVersion, serverVersion, addonDigest, shippedDigest) {
8769
8853
  if (addonDigest !== undefined && addonDigest !== "" && shippedDigest !== undefined) {
8770
8854
  return addonDigest !== shippedDigest;
@@ -8835,7 +8919,7 @@ var init_server_version = __esm(() => {
8835
8919
  GODOT_DEBUG_MODE_DEFAULT = process.env["GODOT_DEBUG"] === "true" || DEBUG_MODE;
8836
8920
  SERVER_VERSION = (() => {
8837
8921
  try {
8838
- const pkg = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf8"));
8922
+ const pkg = JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf8"));
8839
8923
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
8840
8924
  } catch {
8841
8925
  return "0.0.0";
@@ -9620,8 +9704,8 @@ var init_gdscript_source = __esm(() => {
9620
9704
  });
9621
9705
 
9622
9706
  // src/class-cache.ts
9623
- import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
9624
- import { join as join9, posix } from "node:path";
9707
+ import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
9708
+ import { join as join10, posix } from "node:path";
9625
9709
  function isToolScript(source) {
9626
9710
  return TOOL.test(codeOf(source));
9627
9711
  }
@@ -9637,18 +9721,18 @@ function declaredClasses(projectPath) {
9637
9721
  }
9638
9722
  function eachScript(projectPath, each) {
9639
9723
  const visit = (directory, prefix) => {
9640
- if (existsSync9(join9(directory, ".gdignore"))) {
9724
+ if (existsSync10(join10(directory, ".gdignore"))) {
9641
9725
  return;
9642
9726
  }
9643
- for (const entry of readdirSync4(directory, { withFileTypes: true })) {
9727
+ for (const entry of readdirSync5(directory, { withFileTypes: true })) {
9644
9728
  if (entry.name.startsWith(".")) {
9645
9729
  continue;
9646
9730
  }
9647
- const path = join9(directory, entry.name);
9731
+ const path = join10(directory, entry.name);
9648
9732
  if (entry.isDirectory()) {
9649
9733
  visit(path, `${prefix}${entry.name}/`);
9650
9734
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
9651
- each(`res://${prefix}${entry.name}`, readFileSync7(path, "utf8"));
9735
+ each(`res://${prefix}${entry.name}`, readFileSync8(path, "utf8"));
9652
9736
  }
9653
9737
  }
9654
9738
  };
@@ -9670,12 +9754,12 @@ function scriptsNaming(projectPath, classes) {
9670
9754
  return naming.sort((a, b) => a.script.localeCompare(b.script));
9671
9755
  }
9672
9756
  function cachedClasses(projectPath) {
9673
- const cache = join9(projectPath, ".godot", "global_script_class_cache.cfg");
9674
- if (!existsSync9(cache)) {
9757
+ const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
9758
+ if (!existsSync10(cache)) {
9675
9759
  return null;
9676
9760
  }
9677
9761
  const listed = new Map;
9678
- const text = readFileSync7(cache, "utf8");
9762
+ const text = readFileSync8(cache, "utf8");
9679
9763
  for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
9680
9764
  listed.set(entry[1] ?? "", entry[2] ?? "");
9681
9765
  }
@@ -9941,7 +10025,7 @@ function uncachedClassNote(types) {
9941
10025
  return `${types.join(", ")} ${one ? "is declared" : "are declared"} in this project and missing from ` + `.godot/global_script_class_cache.cfg, so a game launched now would not resolve ${one ? "it" : "them"} ` + "either. Try editor_rescan first: it asks the editor already running and starts nothing, and it " + "cleared this in 314ms in a second project with a 31-worker fan-out still importing. It is not " + "always enough, because a file another engine has already imported reads as settled and the " + "walk does not look inside it. project_import refresh_classes rewrites the cache from the " + "declarations on disk and does reach that case, at the cost of a short headless engine, which " + "is worth knowing when something else is already running against this project.";
9942
10026
  }
9943
10027
  function cacheWrittenAt(projectPath) {
9944
- const cache = join9(projectPath, ".godot", "global_script_class_cache.cfg");
10028
+ const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
9945
10029
  try {
9946
10030
  return statSync4(cache).mtimeMs;
9947
10031
  } catch {
@@ -9955,14 +10039,14 @@ function declaredSince(projectPath, paths, names, since) {
9955
10039
  return false;
9956
10040
  }
9957
10041
  try {
9958
- return statSync4(join9(projectPath, path.slice("res://".length))).mtimeMs > since;
10042
+ return statSync4(join10(projectPath, path.slice("res://".length))).mtimeMs > since;
9959
10043
  } catch {
9960
10044
  return false;
9961
10045
  }
9962
10046
  });
9963
10047
  }
9964
10048
  function cachedAtMissingPaths(cached, projectPath) {
9965
- return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync9(join9(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
10049
+ return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync10(join10(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
9966
10050
  }
9967
10051
  function staleAgainst(cached, projectPath) {
9968
10052
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -9987,8 +10071,8 @@ var init_class_cache = __esm(() => {
9987
10071
  });
9988
10072
 
9989
10073
  // src/project-scan.ts
9990
- import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "node:fs";
9991
- import { extname, join as join10 } from "node:path";
10074
+ import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
10075
+ import { extname, join as join11 } from "node:path";
9992
10076
  function skipped(name) {
9993
10077
  return name.startsWith(".") || SKIPPED.has(name);
9994
10078
  }
@@ -9996,10 +10080,10 @@ function steppedOver(directory) {
9996
10080
  return gdignored(directory) || installedByUs(directory);
9997
10081
  }
9998
10082
  function gdignored(directory) {
9999
- return existsSync10(join10(directory, ".gdignore"));
10083
+ return existsSync11(join11(directory, ".gdignore"));
10000
10084
  }
10001
10085
  function installedByUs(directory) {
10002
- return existsSync10(join10(directory, VERSION_MARKER));
10086
+ return existsSync11(join11(directory, VERSION_MARKER));
10003
10087
  }
10004
10088
  function projectStructure(projectPath) {
10005
10089
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -10007,12 +10091,12 @@ function projectStructure(projectPath) {
10007
10091
  if (steppedOver(directory)) {
10008
10092
  return;
10009
10093
  }
10010
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
10094
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10011
10095
  if (skipped(entry.name)) {
10012
10096
  continue;
10013
10097
  }
10014
10098
  if (entry.isDirectory()) {
10015
- visit(join10(directory, entry.name));
10099
+ visit(join11(directory, entry.name));
10016
10100
  } else if (entry.isFile()) {
10017
10101
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10018
10102
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -10036,13 +10120,13 @@ function projectFiles(projectPath, extensions) {
10036
10120
  if (steppedOver(directory)) {
10037
10121
  return;
10038
10122
  }
10039
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
10123
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10040
10124
  if (skipped(entry.name)) {
10041
10125
  continue;
10042
10126
  }
10043
10127
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10044
10128
  if (entry.isDirectory()) {
10045
- visit(join10(directory, entry.name), spelled);
10129
+ visit(join11(directory, entry.name), spelled);
10046
10130
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
10047
10131
  found.push(spelled);
10048
10132
  }
@@ -10057,20 +10141,20 @@ function scriptsWithoutUid(projectPath) {
10057
10141
  if (steppedOver(directory)) {
10058
10142
  return;
10059
10143
  }
10060
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
10144
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10061
10145
  if (skipped(entry.name)) {
10062
10146
  continue;
10063
10147
  }
10064
10148
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10065
10149
  if (entry.isDirectory()) {
10066
- visit(join10(directory, entry.name), spelled);
10150
+ visit(join11(directory, entry.name), spelled);
10067
10151
  continue;
10068
10152
  }
10069
10153
  if (!entry.isFile()) {
10070
10154
  continue;
10071
10155
  }
10072
10156
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10073
- if (CARRIES_A_UID.has(extension) && !existsSync10(join10(directory, `${entry.name}.uid`))) {
10157
+ if (CARRIES_A_UID.has(extension) && !existsSync11(join11(directory, `${entry.name}.uid`))) {
10074
10158
  found.push(spelled);
10075
10159
  }
10076
10160
  }
@@ -10087,11 +10171,11 @@ function projectTexts(projectPath) {
10087
10171
  if (steppedOver(directory)) {
10088
10172
  return;
10089
10173
  }
10090
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
10174
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10091
10175
  if (skipped(entry.name)) {
10092
10176
  continue;
10093
10177
  }
10094
- const absolute = join10(directory, entry.name);
10178
+ const absolute = join11(directory, entry.name);
10095
10179
  const spelled = `${prefix}${entry.name}`;
10096
10180
  if (entry.isDirectory()) {
10097
10181
  visit(absolute, `${spelled}/`);
@@ -10101,7 +10185,7 @@ function projectTexts(projectPath) {
10101
10185
  if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
10102
10186
  continue;
10103
10187
  }
10104
- const bytes = readFileSync8(absolute);
10188
+ const bytes = readFileSync9(absolute);
10105
10189
  if (!isBinary(bytes)) {
10106
10190
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
10107
10191
  }
@@ -10117,11 +10201,11 @@ function ignoredTexts(projectPath) {
10117
10201
  return;
10118
10202
  }
10119
10203
  const under = ignored || gdignored(directory);
10120
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
10204
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10121
10205
  if (skipped(entry.name)) {
10122
10206
  continue;
10123
10207
  }
10124
- const absolute = join10(directory, entry.name);
10208
+ const absolute = join11(directory, entry.name);
10125
10209
  const spelled = `${prefix}${entry.name}`;
10126
10210
  if (entry.isDirectory()) {
10127
10211
  visit(absolute, `${spelled}/`, under);
@@ -10131,7 +10215,7 @@ function ignoredTexts(projectPath) {
10131
10215
  if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
10132
10216
  continue;
10133
10217
  }
10134
- const bytes = readFileSync8(absolute);
10218
+ const bytes = readFileSync9(absolute);
10135
10219
  if (!isBinary(bytes)) {
10136
10220
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
10137
10221
  }
@@ -10154,14 +10238,14 @@ function searchProject(projectPath, options) {
10154
10238
  if (steppedOver(directory)) {
10155
10239
  return;
10156
10240
  }
10157
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
10241
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10158
10242
  if (full()) {
10159
10243
  return;
10160
10244
  }
10161
10245
  if (skipped(entry.name)) {
10162
10246
  continue;
10163
10247
  }
10164
- const entryPath = join10(directory, entry.name);
10248
+ const entryPath = join11(directory, entry.name);
10165
10249
  if (entry.isDirectory()) {
10166
10250
  visit(entryPath);
10167
10251
  continue;
@@ -10170,7 +10254,7 @@ function searchProject(projectPath, options) {
10170
10254
  if (!entry.isFile() || extensions !== null && !extensions.has(extension)) {
10171
10255
  continue;
10172
10256
  }
10173
- const bytes = readFileSync8(entryPath);
10257
+ const bytes = readFileSync9(entryPath);
10174
10258
  if (extensions === null && isBinary(bytes)) {
10175
10259
  continue;
10176
10260
  }
@@ -10257,7 +10341,7 @@ var init_answer_json = __esm(() => {
10257
10341
  });
10258
10342
 
10259
10343
  // src/resources.ts
10260
- import { readFileSync as readFileSync9, realpathSync as realpathSync3 } from "node:fs";
10344
+ import { readFileSync as readFileSync10, realpathSync as realpathSync3 } from "node:fs";
10261
10345
  import { extname as extname2, resolve as resolve3 } from "node:path";
10262
10346
  function ensureProjectPath(getProjectPath) {
10263
10347
  const projectPath = getProjectPath();
@@ -10453,7 +10537,7 @@ function readResourceText(uri, getProjectPath) {
10453
10537
  const parsedUri = parseGodotUri(uri);
10454
10538
  if (parsedUri.kind === "project-info") {
10455
10539
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
10456
- const rawProject = readFileSync9(projectFilePath, "utf-8");
10540
+ const rawProject = readFileSync10(projectFilePath, "utf-8");
10457
10541
  const parsedProject = parseProjectGodot(rawProject);
10458
10542
  return {
10459
10543
  mimeType: "application/json",
@@ -10462,7 +10546,7 @@ function readResourceText(uri, getProjectPath) {
10462
10546
  }
10463
10547
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
10464
10548
  ensureAllowedExtension(parsedUri.kind, filePath);
10465
- const text = readFileSync9(filePath, "utf-8");
10549
+ const text = readFileSync10(filePath, "utf-8");
10466
10550
  return {
10467
10551
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
10468
10552
  text
@@ -10607,16 +10691,16 @@ import { spawnSync } from "node:child_process";
10607
10691
  import { createHash as createHash3 } from "node:crypto";
10608
10692
  import {
10609
10693
  cpSync,
10610
- existsSync as existsSync11,
10694
+ existsSync as existsSync12,
10611
10695
  mkdirSync as mkdirSync4,
10612
- readdirSync as readdirSync6,
10613
- readFileSync as readFileSync10,
10696
+ readdirSync as readdirSync7,
10697
+ readFileSync as readFileSync11,
10614
10698
  rmdirSync,
10615
10699
  rmSync as rmSync5,
10616
10700
  statSync as statSync5,
10617
10701
  writeFileSync as writeFileSync6
10618
10702
  } from "node:fs";
10619
- import { dirname as dirname5, join as join11 } from "node:path";
10703
+ import { dirname as dirname6, join as join12 } from "node:path";
10620
10704
  import { fileURLToPath } from "node:url";
10621
10705
  function editorAddonDigest(from = shippedAddonsDirectory()) {
10622
10706
  return addonsDigest(EDITOR_PLUGINS, from);
@@ -10627,25 +10711,25 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
10627
10711
  function addonsDigest(names, from) {
10628
10712
  const hash = createHash3("sha256");
10629
10713
  const walk = (directory, relative) => {
10630
- for (const name of readdirSync6(directory).sort()) {
10714
+ for (const name of readdirSync7(directory).sort()) {
10631
10715
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
10632
10716
  continue;
10633
10717
  }
10634
- const path = join11(directory, name);
10718
+ const path = join12(directory, name);
10635
10719
  const inside = `${relative}/${name}`;
10636
10720
  if (statSync5(path).isDirectory()) {
10637
10721
  walk(path, inside);
10638
10722
  } else {
10639
10723
  hash.update(`${inside}
10640
10724
  `);
10641
- hash.update(readFileSync10(path));
10725
+ hash.update(readFileSync11(path));
10642
10726
  hash.update(`
10643
10727
  `);
10644
10728
  }
10645
10729
  }
10646
10730
  };
10647
10731
  for (const name of names) {
10648
- walk(join11(from, name), name);
10732
+ walk(join12(from, name), name);
10649
10733
  }
10650
10734
  return hash.digest("hex");
10651
10735
  }
@@ -10676,60 +10760,60 @@ function installedRuntimeDigest(projectPath) {
10676
10760
  return digestMarker(projectPath, RUNTIME_ADDON);
10677
10761
  }
10678
10762
  function digestMarker(projectPath, addon) {
10679
- const marker = join11(projectPath, "addons", addon, DIGEST_MARKER);
10680
- if (!existsSync11(marker)) {
10763
+ const marker = join12(projectPath, "addons", addon, DIGEST_MARKER);
10764
+ if (!existsSync12(marker)) {
10681
10765
  return null;
10682
10766
  }
10683
- const said = readFileSync10(marker, "utf8").trim();
10767
+ const said = readFileSync11(marker, "utf8").trim();
10684
10768
  return said === "" ? null : said;
10685
10769
  }
10686
10770
  function shippedAddonsDirectory() {
10687
- return join11(dirname5(fileURLToPath(import.meta.url)), "godot", "addons");
10771
+ return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "addons");
10688
10772
  }
10689
10773
  function shippedOperationsScript() {
10690
- return join11(dirname5(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
10774
+ return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
10691
10775
  }
10692
10776
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
10693
10777
  const installed = [];
10694
10778
  for (const name of ADDONS) {
10695
- const source = join11(from, name);
10696
- if (!existsSync11(join11(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
10779
+ const source = join12(from, name);
10780
+ if (!existsSync12(join12(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
10697
10781
  throw new Error(`The package holds no ${name} addon at ${source}.`);
10698
10782
  }
10699
- const target = join11(projectPath, "addons", name);
10700
- const replaced = existsSync11(target);
10783
+ const target = join12(projectPath, "addons", name);
10784
+ const replaced = existsSync12(target);
10701
10785
  rmSync5(target, { recursive: true, force: true });
10702
- mkdirSync4(dirname5(target), { recursive: true });
10786
+ mkdirSync4(dirname6(target), { recursive: true });
10703
10787
  cpSync(source, target, { recursive: true });
10704
- writeFileSync6(join11(target, VERSION_MARKER), `${SERVER_VERSION}
10788
+ writeFileSync6(join12(target, VERSION_MARKER), `${SERVER_VERSION}
10705
10789
  `);
10706
10790
  installed.push({ name, path: target, replaced });
10707
10791
  }
10708
- writeFileSync6(join11(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10792
+ writeFileSync6(join12(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10709
10793
  `);
10710
- writeFileSync6(join11(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10794
+ writeFileSync6(join12(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10711
10795
  `);
10712
10796
  return installed;
10713
10797
  }
10714
10798
  function installedAddonVersion(projectPath) {
10715
- const marker = join11(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10716
- if (!existsSync11(marker)) {
10799
+ const marker = join12(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10800
+ if (!existsSync12(marker)) {
10717
10801
  return null;
10718
10802
  }
10719
- const said = readFileSync10(marker, "utf8").trim();
10803
+ const said = readFileSync11(marker, "utf8").trim();
10720
10804
  return said === "" ? null : said;
10721
10805
  }
10722
10806
  function removeAddons(projectPath) {
10723
10807
  const removed = [];
10724
10808
  for (const name of ADDONS) {
10725
- const target = join11(projectPath, "addons", name);
10726
- if (existsSync11(target)) {
10809
+ const target = join12(projectPath, "addons", name);
10810
+ if (existsSync12(target)) {
10727
10811
  rmSync5(target, { recursive: true, force: true });
10728
10812
  removed.push(target);
10729
10813
  }
10730
10814
  }
10731
- const addons = join11(projectPath, "addons");
10732
- if (removed.length > 0 && existsSync11(addons) && readdirSync6(addons).length === 0) {
10815
+ const addons = join12(projectPath, "addons");
10816
+ if (removed.length > 0 && existsSync12(addons) && readdirSync7(addons).length === 0) {
10733
10817
  rmdirSync(addons);
10734
10818
  removed.push(addons);
10735
10819
  }
@@ -10763,7 +10847,7 @@ function loaderAmong(projectPath, named) {
10763
10847
  }
10764
10848
  let source = null;
10765
10849
  try {
10766
- source = readFileSync10(join11(projectPath, path), "utf8");
10850
+ source = readFileSync11(join12(projectPath, path), "utf8");
10767
10851
  } catch {}
10768
10852
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
10769
10853
  return { name: entry, path, exists: true };
@@ -10805,10 +10889,10 @@ function enabledPlugins(settings) {
10805
10889
  function inspectProject(projectPath) {
10806
10890
  const problems = [];
10807
10891
  const addons = ADDONS.map((name) => {
10808
- const directory = join11(projectPath, "addons", name);
10809
- const marker = join11(directory, VERSION_MARKER);
10810
- const installed = existsSync11(directory);
10811
- const version = existsSync11(marker) ? readFileSync10(marker, "utf8").trim() : null;
10892
+ const directory = join12(projectPath, "addons", name);
10893
+ const marker = join12(directory, VERSION_MARKER);
10894
+ const installed = existsSync12(directory);
10895
+ const version = existsSync12(marker) ? readFileSync11(marker, "utf8").trim() : null;
10812
10896
  const current = version === SERVER_VERSION;
10813
10897
  if (!installed) {
10814
10898
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -10817,7 +10901,7 @@ function inspectProject(projectPath) {
10817
10901
  }
10818
10902
  return { name, installed, version, current };
10819
10903
  });
10820
- const settings = parseProjectGodot(readFileSync10(join11(projectPath, "project.godot"), "utf8"));
10904
+ const settings = parseProjectGodot(readFileSync11(join12(projectPath, "project.godot"), "utf8"));
10821
10905
  const pluginsEnabled = enabledPlugins(settings);
10822
10906
  for (const name of EDITOR_PLUGINS) {
10823
10907
  if (!pluginsEnabled.includes(name)) {
@@ -10833,7 +10917,7 @@ function inspectProject(projectPath) {
10833
10917
  const tracked = trackedByGit(projectPath, [...named.values()]);
10834
10918
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
10835
10919
  for (const [name, path] of named) {
10836
- if (!existsSync11(join11(projectPath, path))) {
10920
+ if (!existsSync12(join12(projectPath, path))) {
10837
10921
  if (!addonMissing(path)) {
10838
10922
  problems.push(`the ${name} autoload names res://${path}, which is not in this project, so it boots with a missing script; restore that file, or point the autoload at one that is here`);
10839
10923
  }
@@ -11898,7 +11982,7 @@ var init_tool_definitions = __esm(() => {
11898
11982
  type: "array",
11899
11983
  items: { type: "string" },
11900
11984
  ops: ["start", "check"],
11901
- description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here, so a flag the engine reads goes to the game instead, and the answer says so under argsNote. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so the debug_* tools do not answer for it.`
11985
+ description: `The game's own arguments, what OS.get_cmdline_user_args() answers, such as ["--level=2"]. The separator is added here, so a flag the engine reads goes to the game instead, and the answer says so under argsNote. A run with any is started by this server rather than by the editor, which fixes the game's command line when it opens the project, so its debugger is the keeper holding it rather than the editor; the debug_* tools answer for it all the same.`
11902
11986
  },
11903
11987
  engineArgs: {
11904
11988
  type: "array",
@@ -11915,7 +11999,7 @@ var init_tool_definitions = __esm(() => {
11915
11999
  silent: {
11916
12000
  type: "boolean",
11917
12001
  ops: ["start"],
11918
- description: "start: run the game with no sound out of the machine, by starting the engine with --audio-driver Dummy: every stream still plays and every playback position still advances, so what is playing can still be asked. The editor takes no engine flags for a game it plays, so a silent windowed run is started by this server, and the debug_* tools do not answer for it. A headless run, check's included, is silent already. Default false, or true when GDHARNESS_SILENT is set in the server's environment to anything but 0 or false; silent: false then plays through the editor as before."
12002
+ description: "start: run the game with no sound out of the machine, by starting the engine with --audio-driver Dummy: every stream still plays and every playback position still advances, so what is playing can still be asked. The editor takes no engine flags for a game it plays, so a silent windowed run is started by this server, whose keeper is its debugger rather than the editor. A headless run, check's included, is silent already. Default false, or true when GDHARNESS_SILENT is set in the server's environment to anything but 0 or false; silent: false then plays through the editor as before."
11919
12003
  },
11920
12004
  savesIn: {
11921
12005
  type: "string",
@@ -12241,12 +12325,12 @@ var init_tool_definitions = __esm(() => {
12241
12325
  },
12242
12326
  metrics: {
12243
12327
  effect: "read",
12244
- summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
12328
+ summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game itself, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. physics_steps_per_second is the physics steps taken a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. The memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
12245
12329
  requires: []
12246
12330
  },
12247
12331
  accessibility: {
12248
12332
  effect: "read",
12249
- summary: `what the game tells a screen reader, read through UI Automation as a Windows screen reader reads it, under windows: one tree per window, each element with its role as UI Automation names it (text for static text, button, check_box, group for a control holding others), its name, its value where it has one, its bounds in the window's pixels, the actions a screen reader is offered (click, focus, toggle, set_value, select, expand_collapse, scroll_into_view), and focused on the one holding the focus. The name is what is read aloud, so an empty one is read as nothing; static text takes it from DisplayServer.accessibility_update_set_value, not from a name set on it, and the answer says under _note how many text elements on screen have none. With no control focused, the engine reports the first node under the root as focused, which in a run with the runtime is GdharnessRuntime. Needs a windowed run on Windows started with engineArgs ["--accessibility", "always"] or with the project's accessibility/general/accessibility_support at Always Active, since the engine builds the tree only when asked; reading it takes a second or two, starting PowerShell inside the game so it reaches the game's own desktop`,
12333
+ summary: "what the game tells a screen reader, read through UI Automation as a Windows screen reader reads it, under windows: one tree per window, each element with its role (text for static text, button, check_box, group for a control holding others), name, value, description, placeholder, bounds in the window's pixels, the actions a reader is offered (click, focus, toggle, set_value, select, expand_collapse, scroll_into_view), and focused on the one with the focus. node is the path of the node that made the element; an element a node made as a part of itself has part_of instead, and part: announcement marks the text a window speaks announcements through. An empty name is read as nothing: static text takes it from accessibility_update_set_value, and _note counts the text elements on screen with none. tooltip is the node's tooltip_text, which a Windows reader is not told. With no control focused, the engine reports the first node under the root, GdharnessRuntime, as focused. Needs a windowed run on Windows started with engineArgs [\"--accessibility\", \"always\"] or accessibility_support at Always Active, since the engine builds the tree only when asked; reading takes a second or two",
12250
12334
  requires: []
12251
12335
  }
12252
12336
  },
@@ -12596,7 +12680,7 @@ var init_tool_definitions = __esm(() => {
12596
12680
  {
12597
12681
  name: "debug_breakpoint",
12598
12682
  title: "Breakpoints",
12599
- description: "Sets or removes a breakpoint through the editor's debug adapter. Needs the editor, not a running game: set them first, then editor_run, and the game stops where you asked. The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor running an older addon is said so under breakpointsAtRisk, here and in editor_status.",
12683
+ description: "Sets or removes a breakpoint. Set them first, then editor_run, and the game stops where you asked. With the editor connected it goes through the editor's debug adapter; with none, it is held here. A game this server starts itself, which editor_run start does for a run given args, savesIn, env or silent, or with no editor, is handed every breakpoint held here as it starts, and one set while that game runs reaches it at once (runningGame says so). The editor keeps a breakpoint set this way for one play, so every breakpoint this server holds is sent again before each play editor_run starts, and the start answer lists them under breakpoints. The set is also kept in the project, for the server that comes after a reconnect: a breakpoint stays until it is removed here, whichever server set it. Every answer lists the whole set under held, and the breakpoints the user set in the editor's own gutter under setInEditor; setting one here never takes those away, and removing a line here clears it whoever set it. Godot clears every breakpoint in the editor when a debug session opens unless network/debug_adapter/sync_breakpoints is on, which the addon turns on as it loads; an editor running an older addon is said so under breakpointsAtRisk, here and in editor_status.",
12600
12684
  parameters: {
12601
12685
  projectPath: PROJECT_PATH,
12602
12686
  scriptPath: SCRIPT_PATH,
@@ -12611,23 +12695,33 @@ var init_tool_definitions = __esm(() => {
12611
12695
  {
12612
12696
  name: "debug_control",
12613
12697
  title: "Step the debugger",
12614
- description: "Continues or steps the debugged game through the editor's debug adapter. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. There is no pause and no step_out: Godot's adapter answers a pause by reporting the game stopped and leaving it running, and implements no stepOut at all, so hold the game where you want it with a breakpoint and step over or into from there.",
12698
+ description: "Pauses, continues or steps the debugged game. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. A game the editor plays is moved through the editor's debug adapter, which cannot pause or step out: it answers a pause by reporting the game stopped and leaving it running, and implements no stepOut, so hold such a game with a breakpoint and step over or into from there. A game this server started itself has its keeper as its debugger, which does both: pause breaks into the game wherever it is, a script spinning in a loop included, which is how a game hung on its first frame is asked where it is.",
12615
12699
  parameters: {},
12616
12700
  requires: [],
12617
12701
  operations: {
12702
+ pause: {
12703
+ effect: "additive",
12704
+ summary: "stop a game this server started where it is, answering with the stack. The game takes it between frames, and in a script every 2048 lines it runs, so a loop that sleeps between few lines can take seconds; the call waits thirty, and past that answers paused false with the request still standing. Stopped between frames, where no script runs, the game is stepped on to the next line of script it runs",
12705
+ requires: []
12706
+ },
12618
12707
  continue: { effect: "additive", summary: "resume after a breakpoint", requires: [] },
12619
12708
  step_over: { effect: "additive", summary: "run the current line", requires: [] },
12620
12709
  step_into: {
12621
12710
  effect: "additive",
12622
12711
  summary: "run the current line, stopping inside whatever it calls",
12623
12712
  requires: []
12713
+ },
12714
+ step_out: {
12715
+ effect: "additive",
12716
+ summary: "run to the end of the current function and stop in its caller; a game this server started only",
12717
+ requires: []
12624
12718
  }
12625
12719
  }
12626
12720
  },
12627
12721
  {
12628
12722
  name: "debug_state",
12629
12723
  title: "Debugger state",
12630
- description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debug adapter's console output so far, its last thousand lines, with any earlier ones counted under notShown. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack.",
12724
+ description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debugger's console output so far, its last thousand lines, with any earlier ones counted under notShown. For a game this server started, its keeper's debugger answers: each frame names its script as the project spells it under script and on this machine under path, and held says which thread stopped and why. For a game the editor plays, the editor's debug adapter answers. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack.",
12631
12725
  parameters: {
12632
12726
  frameId: {
12633
12727
  type: "number",
@@ -34412,10 +34506,10 @@ var init_stdio = __esm(() => {
34412
34506
  });
34413
34507
 
34414
34508
  // src/runtime-client.ts
34415
- import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync12, statSync as statSync6, unlinkSync } from "node:fs";
34509
+ import { existsSync as existsSync14, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync6, unlinkSync as unlinkSync2 } from "node:fs";
34416
34510
  import { createConnection } from "node:net";
34417
34511
  import { tmpdir as tmpdir4 } from "node:os";
34418
- import { dirname as dirname8, join as join14, resolve as resolve4 } from "node:path";
34512
+ import { dirname as dirname9, join as join15, resolve as resolve4 } from "node:path";
34419
34513
  import { setTimeout as delay } from "node:timers/promises";
34420
34514
  function runtimeDirectory(variables = process.env) {
34421
34515
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -34423,20 +34517,20 @@ function runtimeDirectory(variables = process.env) {
34423
34517
  return explicit;
34424
34518
  }
34425
34519
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
34426
- return join14(perUser ?? tmpdir4(), "gdharness");
34520
+ return join15(perUser ?? tmpdir4(), "gdharness");
34427
34521
  }
34428
34522
  function runtimeDirectories(variables = process.env) {
34429
34523
  const candidates = [runtimeDirectory(variables)];
34430
34524
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
34431
34525
  for (const base of fallbacks) {
34432
- candidates.push(join14(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34526
+ candidates.push(join15(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34433
34527
  }
34434
34528
  return [...new Set(candidates.map((path) => resolve4(path)))];
34435
34529
  }
34436
34530
  function errorReportOf(pid, directories = runtimeDirectories()) {
34437
34531
  for (const directory of directories) {
34438
- const path = join14(directory, `runtime-${pid}.log`);
34439
- if (existsSync13(path)) {
34532
+ const path = join15(directory, `runtime-${pid}.log`);
34533
+ if (existsSync14(path)) {
34440
34534
  return path;
34441
34535
  }
34442
34536
  }
@@ -34467,7 +34561,7 @@ function reportAlreadyInTranscript(transcript, report) {
34467
34561
  function parseAnnouncement(file, pid) {
34468
34562
  let fields;
34469
34563
  try {
34470
- fields = asParams(JSON.parse(readFileSync12(file, "utf8")));
34564
+ fields = asParams(JSON.parse(readFileSync13(file, "utf8")));
34471
34565
  } catch {
34472
34566
  return { kind: "rubbish" };
34473
34567
  }
@@ -34533,7 +34627,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
34533
34627
  function wentAway(file, pid) {
34534
34628
  let project = "";
34535
34629
  try {
34536
- const fields = asParams(JSON.parse(readFileSync12(file, "utf8")));
34630
+ const fields = asParams(JSON.parse(readFileSync13(file, "utf8")));
34537
34631
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
34538
34632
  } catch {
34539
34633
  return;
@@ -34576,23 +34670,23 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
34576
34670
  }).map((one) => one.file));
34577
34671
  }
34578
34672
  function announcedIn(directory) {
34579
- if (!existsSync13(directory)) {
34673
+ if (!existsSync14(directory)) {
34580
34674
  return [];
34581
34675
  }
34582
34676
  const found = [];
34583
34677
  const candidates = [];
34584
34678
  const entries = [];
34585
- for (const entry of readdirSync8(directory)) {
34679
+ for (const entry of readdirSync9(directory)) {
34586
34680
  const report = ERROR_REPORT_PATTERN.exec(entry);
34587
34681
  if (report) {
34588
- sweepErrorReport(join14(directory, entry), Number.parseInt(report[1] ?? "", 10));
34682
+ sweepErrorReport(join15(directory, entry), Number.parseInt(report[1] ?? "", 10));
34589
34683
  continue;
34590
34684
  }
34591
34685
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
34592
34686
  if (!match) {
34593
34687
  continue;
34594
34688
  }
34595
- const file = join14(directory, entry);
34689
+ const file = join15(directory, entry);
34596
34690
  const pid = Number.parseInt(match[1] ?? "", 10);
34597
34691
  const running = alive(pid);
34598
34692
  entries.push({ file, pid, running });
@@ -34612,7 +34706,7 @@ function announcedIn(directory) {
34612
34706
  }
34613
34707
  wentAway(file, pid);
34614
34708
  try {
34615
- unlinkSync(file);
34709
+ unlinkSync2(file);
34616
34710
  } catch {}
34617
34711
  verdicts.delete(file);
34618
34712
  continue;
@@ -34620,16 +34714,16 @@ function announcedIn(directory) {
34620
34714
  found.push(announced);
34621
34715
  }
34622
34716
  const held = new Set(entries.map((one) => one.file));
34623
- const here = join14(directory, ".");
34717
+ const here = join15(directory, ".");
34624
34718
  for (const file of verdicts.keys()) {
34625
- if (dirname8(file) === here && !held.has(file)) {
34719
+ if (dirname9(file) === here && !held.has(file)) {
34626
34720
  verdicts.delete(file);
34627
34721
  }
34628
34722
  }
34629
34723
  return found;
34630
34724
  }
34631
34725
  async function announcementEnded(pid, directories = runtimeDirectories()) {
34632
- const files = directories.map((directory) => join14(directory, `runtime-${pid}.json`)).filter((file) => existsSync13(file));
34726
+ const files = directories.map((directory) => join15(directory, `runtime-${pid}.json`)).filter((file) => existsSync14(file));
34633
34727
  const first = files[0];
34634
34728
  if (first === undefined) {
34635
34729
  return;
@@ -34645,7 +34739,7 @@ async function announcementEnded(pid, directories = runtimeDirectories()) {
34645
34739
  wentAway(first, pid);
34646
34740
  for (const file of files) {
34647
34741
  try {
34648
- unlinkSync(file);
34742
+ unlinkSync2(file);
34649
34743
  } catch {}
34650
34744
  verdicts.delete(file);
34651
34745
  }
@@ -34656,7 +34750,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
34656
34750
  }
34657
34751
  try {
34658
34752
  if (now - statSync6(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
34659
- unlinkSync(path);
34753
+ unlinkSync2(path);
34660
34754
  }
34661
34755
  } catch {}
34662
34756
  }
@@ -34930,14 +35024,14 @@ var init_runtime_client = __esm(() => {
34930
35024
  });
34931
35025
 
34932
35026
  // src/boot-note.ts
34933
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
34934
- import { dirname as dirname9, join as join15 } from "node:path";
35027
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
35028
+ import { dirname as dirname10, join as join16 } from "node:path";
34935
35029
  function bootNotePath(projectPath) {
34936
- return join15(projectPath, ".godot", "gdharness-boot.json");
35030
+ return join16(projectPath, ".godot", "gdharness-boot.json");
34937
35031
  }
34938
35032
  function readScenes(projectPath) {
34939
35033
  try {
34940
- return asParams(asParams(JSON.parse(readFileSync13(bootNotePath(projectPath), "utf8")))["scenes"]);
35034
+ return asParams(asParams(JSON.parse(readFileSync14(bootNotePath(projectPath), "utf8")))["scenes"]);
34941
35035
  } catch {
34942
35036
  return {};
34943
35037
  }
@@ -34953,7 +35047,7 @@ function readBootNote(projectPath, scene) {
34953
35047
  function writeBootNote(projectPath, scene, announcedAfterMs) {
34954
35048
  try {
34955
35049
  const path = bootNotePath(projectPath);
34956
- mkdirSync6(dirname9(path), { recursive: true });
35050
+ mkdirSync6(dirname10(path), { recursive: true });
34957
35051
  const scenes = {
34958
35052
  ...readScenes(projectPath),
34959
35053
  [scene]: {
@@ -34979,10 +35073,10 @@ var init_boot_note = __esm(() => {
34979
35073
  });
34980
35074
 
34981
35075
  // src/breakpoint-note.ts
34982
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync14, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
34983
- import { dirname as dirname10, isAbsolute as isAbsolute2, join as join16, relative as relative3 } from "node:path";
35076
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
35077
+ import { dirname as dirname11, isAbsolute as isAbsolute3, join as join17, relative as relative3 } from "node:path";
34984
35078
  function breakpointNotePath(projectPath) {
34985
- return join16(projectPath, ".godot", "gdharness-breakpoints.json");
35079
+ return join17(projectPath, ".godot", "gdharness-breakpoints.json");
34986
35080
  }
34987
35081
  function writeBreakpointNote(projectPath, held) {
34988
35082
  const path = breakpointNotePath(projectPath);
@@ -34999,7 +35093,7 @@ function writeBreakpointNote(projectPath, held) {
34999
35093
  lines: [...one.lines]
35000
35094
  }))
35001
35095
  };
35002
- mkdirSync7(dirname10(path), { recursive: true });
35096
+ mkdirSync7(dirname11(path), { recursive: true });
35003
35097
  writeFileSync9(path, `${JSON.stringify(note, null, 2)}
35004
35098
  `, "utf8");
35005
35099
  } catch (error) {
@@ -35008,7 +35102,7 @@ function writeBreakpointNote(projectPath, held) {
35008
35102
  }
35009
35103
  function readBreakpointNote(projectPath) {
35010
35104
  try {
35011
- const parsed = JSON.parse(readFileSync14(breakpointNotePath(projectPath), "utf8"));
35105
+ const parsed = JSON.parse(readFileSync15(breakpointNotePath(projectPath), "utf8"));
35012
35106
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
35013
35107
  return [];
35014
35108
  }
@@ -35018,12 +35112,12 @@ function readBreakpointNote(projectPath) {
35018
35112
  continue;
35019
35113
  }
35020
35114
  const { script, lines: listed } = entry;
35021
- if (typeof script !== "string" || isAbsolute2(script) || script.split("/").includes("..")) {
35115
+ if (typeof script !== "string" || isAbsolute3(script) || script.split("/").includes("..")) {
35022
35116
  continue;
35023
35117
  }
35024
35118
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
35025
35119
  if (lines.length > 0) {
35026
- held.push({ scriptPath: realPathOr(join16(projectPath, script)), lines });
35120
+ held.push({ scriptPath: realPathOr(join17(projectPath, script)), lines });
35027
35121
  }
35028
35122
  }
35029
35123
  return held;
@@ -35033,7 +35127,7 @@ function readBreakpointNote(projectPath) {
35033
35127
  }
35034
35128
  function withinProject(projectPath, scriptPath) {
35035
35129
  const rel = relative3(projectPath, scriptPath);
35036
- return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
35130
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
35037
35131
  }
35038
35132
  var init_breakpoint_note = __esm(() => {
35039
35133
  init_errors();
@@ -35041,14 +35135,14 @@ var init_breakpoint_note = __esm(() => {
35041
35135
  });
35042
35136
 
35043
35137
  // src/class-note.ts
35044
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "node:fs";
35045
- import { dirname as dirname11, join as join17 } from "node:path";
35138
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "node:fs";
35139
+ import { dirname as dirname12, join as join18 } from "node:path";
35046
35140
  function classNotePath(projectPath) {
35047
- return join17(projectPath, ".godot", "gdharness-classes.json");
35141
+ return join18(projectPath, ".godot", "gdharness-classes.json");
35048
35142
  }
35049
35143
  function readClassNote(projectPath) {
35050
35144
  try {
35051
- const fields = asParams(JSON.parse(readFileSync15(classNotePath(projectPath), "utf8")));
35145
+ const fields = asParams(JSON.parse(readFileSync16(classNotePath(projectPath), "utf8")));
35052
35146
  const rebuiltWith = readArray(fields, "rebuiltWith");
35053
35147
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
35054
35148
  return null;
@@ -35065,7 +35159,7 @@ function readClassNote(projectPath) {
35065
35159
  function writeClassNote(projectPath, note) {
35066
35160
  try {
35067
35161
  const path = classNotePath(projectPath);
35068
- mkdirSync8(dirname11(path), { recursive: true });
35162
+ mkdirSync8(dirname12(path), { recursive: true });
35069
35163
  writeFileSync10(path, `${JSON.stringify(note, null, 2)}
35070
35164
  `, "utf8");
35071
35165
  } catch {}
@@ -35073,16 +35167,16 @@ function writeClassNote(projectPath, note) {
35073
35167
  var init_class_note = () => {};
35074
35168
 
35075
35169
  // src/config-pin.ts
35076
- import { existsSync as existsSync14, readFileSync as readFileSync16 } from "node:fs";
35077
- import { join as join18 } from "node:path";
35170
+ import { existsSync as existsSync15, readFileSync as readFileSync17 } from "node:fs";
35171
+ import { join as join19 } from "node:path";
35078
35172
  function versionsNamedInConfig(projectPath) {
35079
- const config = join18(projectPath, ".mcp.json");
35080
- if (!existsSync14(config)) {
35173
+ const config = join19(projectPath, ".mcp.json");
35174
+ if (!existsSync15(config)) {
35081
35175
  return [];
35082
35176
  }
35083
35177
  let text;
35084
35178
  try {
35085
- text = readFileSync16(config, "utf8");
35179
+ text = readFileSync17(config, "utf8");
35086
35180
  JSON.parse(text);
35087
35181
  } catch {
35088
35182
  return [];
@@ -35103,26 +35197,26 @@ var init_config_pin = __esm(() => {
35103
35197
  });
35104
35198
 
35105
35199
  // src/cut-test-run.ts
35106
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync17, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
35107
- import { dirname as dirname12, join as join19 } from "node:path";
35200
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync8, writeFileSync as writeFileSync11 } from "node:fs";
35201
+ import { dirname as dirname13, join as join20 } from "node:path";
35108
35202
  function keptAt(projectPath) {
35109
- return join19(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35203
+ return join20(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35110
35204
  }
35111
35205
  function keepCutTestRun(projectPath, run) {
35112
35206
  const path = keptAt(projectPath);
35113
35207
  try {
35114
- mkdirSync9(dirname12(path), { recursive: true });
35208
+ mkdirSync9(dirname13(path), { recursive: true });
35115
35209
  writeFileSync11(path, `${JSON.stringify(run, null, 2)}
35116
35210
  `, "utf8");
35117
35211
  } catch {}
35118
35212
  }
35119
35213
  function takeCutTestRun(projectPath) {
35120
35214
  const path = keptAt(projectPath);
35121
- if (!existsSync15(path)) {
35215
+ if (!existsSync16(path)) {
35122
35216
  return null;
35123
35217
  }
35124
35218
  try {
35125
- const run = JSON.parse(readFileSync17(path, "utf8"));
35219
+ const run = JSON.parse(readFileSync18(path, "utf8"));
35126
35220
  rmSync8(path, { force: true });
35127
35221
  return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
35128
35222
  } catch {
@@ -35698,6 +35792,18 @@ class GodotDAPClient {
35698
35792
  lines: Array.from(lines).sort((a, b) => a - b)
35699
35793
  }));
35700
35794
  }
35795
+ forgetBreakpoint(scriptPath, line) {
35796
+ const key = fileKey(scriptPath);
35797
+ const lines = this.breakpoints.get(key);
35798
+ if (lines === undefined) {
35799
+ return;
35800
+ }
35801
+ lines.delete(line);
35802
+ if (lines.size === 0) {
35803
+ this.breakpoints.delete(key);
35804
+ this.spelled.delete(key);
35805
+ }
35806
+ }
35701
35807
  holdBreakpoints(held) {
35702
35808
  for (const { scriptPath, lines } of held) {
35703
35809
  const key = fileKey(scriptPath);
@@ -35926,25 +36032,25 @@ var init_desktop = () => {};
35926
36032
  // src/editor-log.ts
35927
36033
  import {
35928
36034
  closeSync,
35929
- existsSync as existsSync16,
36035
+ existsSync as existsSync17,
35930
36036
  mkdirSync as mkdirSync10,
35931
36037
  openSync,
35932
- readFileSync as readFileSync18,
36038
+ readFileSync as readFileSync19,
35933
36039
  readSync,
35934
36040
  rmSync as rmSync9,
35935
36041
  statSync as statSync7,
35936
36042
  writeFileSync as writeFileSync12
35937
36043
  } from "node:fs";
35938
- import { dirname as dirname13, join as join20 } from "node:path";
36044
+ import { dirname as dirname14, join as join21 } from "node:path";
35939
36045
  function editorLogPath(projectPath) {
35940
- return join20(projectPath, ".godot", "gdharness-editor.log");
36046
+ return join21(projectPath, ".godot", "gdharness-editor.log");
35941
36047
  }
35942
36048
  function editorLogNotePath(projectPath) {
35943
- return join20(projectPath, ".godot", "gdharness-editor.json");
36049
+ return join21(projectPath, ".godot", "gdharness-editor.json");
35944
36050
  }
35945
36051
  function readEditorLogNote(projectPath) {
35946
36052
  try {
35947
- const fields = asParams(JSON.parse(readFileSync18(editorLogNotePath(projectPath), "utf8")));
36053
+ const fields = asParams(JSON.parse(readFileSync19(editorLogNotePath(projectPath), "utf8")));
35948
36054
  const pid = readNumber(fields, "pid");
35949
36055
  const startedAt = readString(fields, "startedAt");
35950
36056
  return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
@@ -35955,7 +36061,7 @@ function readEditorLogNote(projectPath) {
35955
36061
  function clearEditorLog(projectPath) {
35956
36062
  try {
35957
36063
  const path = editorLogPath(projectPath);
35958
- mkdirSync10(dirname13(path), { recursive: true });
36064
+ mkdirSync10(dirname14(path), { recursive: true });
35959
36065
  rmSync9(path, { force: true });
35960
36066
  rmSync9(editorLogNotePath(projectPath), { force: true });
35961
36067
  } catch {}
@@ -35963,7 +36069,7 @@ function clearEditorLog(projectPath) {
35963
36069
  function writeEditorLogNote(projectPath, pid) {
35964
36070
  try {
35965
36071
  const path = editorLogNotePath(projectPath);
35966
- mkdirSync10(dirname13(path), { recursive: true });
36072
+ mkdirSync10(dirname14(path), { recursive: true });
35967
36073
  writeFileSync12(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
35968
36074
  } catch {}
35969
36075
  }
@@ -35985,7 +36091,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
35985
36091
  return { kind: "older server", notedPid: noted?.pid ?? null };
35986
36092
  }
35987
36093
  const path = editorLogPath(projectPath);
35988
- if (!existsSync16(path)) {
36094
+ if (!existsSync17(path)) {
35989
36095
  return { kind: "not written" };
35990
36096
  }
35991
36097
  const log = new GameLog;
@@ -36043,7 +36149,7 @@ function foldProgress(text) {
36043
36149
  function theFirstOf(path) {
36044
36150
  const whole = statSync7(path).size;
36045
36151
  if (whole <= MOST_CONSOLE_BYTES) {
36046
- return { text: readFileSync18(path, "utf8") };
36152
+ return { text: readFileSync19(path, "utf8") };
36047
36153
  }
36048
36154
  const handle = openSync(path, "r");
36049
36155
  try {
@@ -36203,6 +36309,26 @@ var init_editor_log = __esm(() => {
36203
36309
  SLOTS = [/"[^"]*"/g, /'[^']*'/g, /\b(?:res|user):\/\/\S+/g, /(?<=\.gd|\.tscn|\.cs):\d+(?::\d+)?/g];
36204
36310
  });
36205
36311
 
36312
+ // src/godot-variant.ts
36313
+ var FLAG_64, MOST_MESSAGE_BYTES;
36314
+ var init_godot_variant = __esm(() => {
36315
+ FLAG_64 = 1 << 16;
36316
+ MOST_MESSAGE_BYTES = (8 << 20) + 4;
36317
+ });
36318
+
36319
+ // src/game-debugger.ts
36320
+ var PAUSE_MS = 30000, STEP_MS = 1e4, READ_MS = 5000, REPLIES;
36321
+ var init_game_debugger = __esm(() => {
36322
+ init_godot_variant();
36323
+ REPLIES = new Set([
36324
+ "debug_enter",
36325
+ "debug_exit",
36326
+ "stack_dump",
36327
+ "stack_frame_vars",
36328
+ "stack_frame_var"
36329
+ ]);
36330
+ });
36331
+
36206
36332
  // node_modules/ws/lib/constants.js
36207
36333
  var require_constants = __commonJS(function(exports, module) {
36208
36334
  var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
@@ -40167,7 +40293,30 @@ var init_junit = __esm(() => {
40167
40293
 
40168
40294
  // src/keeper-channel.ts
40169
40295
  import { connect as connect2, createServer as createServer2 } from "node:net";
40170
- function askTheKeeperToStop(address, withinMs) {
40296
+ async function askTheKeeperToStop(address, withinMs) {
40297
+ const answered = await askTheKeeper(address, "stop", withinMs);
40298
+ if (typeof answered !== "string") {
40299
+ return answered;
40300
+ }
40301
+ return answered === "signalled" || answered === "gone" ? answered : { unreached: `it answered ${JSON.stringify(answered)}` };
40302
+ }
40303
+ async function askTheKeepersDebugger(address, request, withinMs) {
40304
+ const answered = await askTheKeeper(address, `${DEBUG_PREFIX}${JSON.stringify(request)}`, withinMs);
40305
+ if (typeof answered !== "string") {
40306
+ return answered;
40307
+ }
40308
+ if (answered === "unknown") {
40309
+ return { unreached: "its keeper was started by a gdharness from before game debuggers, and has none" };
40310
+ }
40311
+ try {
40312
+ const parsed = JSON.parse(answered);
40313
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
40314
+ return parsed;
40315
+ }
40316
+ } catch {}
40317
+ return { unreached: `it answered ${JSON.stringify(answered.slice(0, 200))}` };
40318
+ }
40319
+ function askTheKeeper(address, line, withinMs) {
40171
40320
  return new Promise((resolve) => {
40172
40321
  let said = "";
40173
40322
  let settled = false;
@@ -40185,16 +40334,15 @@ function askTheKeeperToStop(address, withinMs) {
40185
40334
  }, withinMs);
40186
40335
  socket.setEncoding("utf8");
40187
40336
  socket.on("connect", () => {
40188
- socket.write(`stop
40337
+ socket.write(`${line}
40189
40338
  `);
40190
40339
  });
40191
40340
  socket.on("data", (chunk) => {
40192
40341
  said += chunk;
40193
- const line = said.split(`
40194
- `)[0] ?? "";
40195
40342
  if (said.includes(`
40196
40343
  `)) {
40197
- settle(line === "signalled" || line === "gone" ? line : { unreached: `it answered ${JSON.stringify(line)}` });
40344
+ settle(said.split(`
40345
+ `)[0] ?? "");
40198
40346
  }
40199
40347
  });
40200
40348
  socket.on("error", (error) => {
@@ -40205,22 +40353,23 @@ function askTheKeeperToStop(address, withinMs) {
40205
40353
  });
40206
40354
  });
40207
40355
  }
40356
+ var DEBUG_PREFIX = "debug ";
40208
40357
  var init_keeper_channel = () => {};
40209
40358
 
40210
40359
  // src/lifecycle-log.ts
40211
- import { appendFileSync, mkdirSync as mkdirSync11, renameSync, statSync as statSync8 } from "node:fs";
40212
- import { dirname as dirname14, join as join21 } from "node:path";
40360
+ import { appendFileSync, mkdirSync as mkdirSync11, renameSync as renameSync2, statSync as statSync8 } from "node:fs";
40361
+ import { dirname as dirname15, join as join22 } from "node:path";
40213
40362
  import process10 from "node:process";
40214
40363
  function lifecycleLogPath() {
40215
- return join21(runtimeDirectory(), "servers.log");
40364
+ return join22(runtimeDirectory(), "servers.log");
40216
40365
  }
40217
40366
  function writeLifecycle(event, fields = {}) {
40218
40367
  const path = lifecycleLogPath();
40219
40368
  try {
40220
- mkdirSync11(dirname14(path), { recursive: true });
40369
+ mkdirSync11(dirname15(path), { recursive: true });
40221
40370
  if ((statSync8(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
40222
40371
  try {
40223
- renameSync(path, `${path}.1`);
40372
+ renameSync2(path, `${path}.1`);
40224
40373
  } catch {}
40225
40374
  }
40226
40375
  const line = {
@@ -40255,10 +40404,10 @@ var init_lifecycle_log = __esm(() => {
40255
40404
  });
40256
40405
 
40257
40406
  // src/lsp_client.ts
40258
- import { existsSync as existsSync17, realpathSync as realpathSync4 } from "node:fs";
40407
+ import { existsSync as existsSync18, realpathSync as realpathSync4 } from "node:fs";
40259
40408
  import { readFile, realpath } from "node:fs/promises";
40260
40409
  import { createConnection as createConnection3 } from "node:net";
40261
- import { dirname as dirname15, join as join22, resolve as resolve5 } from "node:path";
40410
+ import { dirname as dirname16, join as join23, resolve as resolve5 } from "node:path";
40262
40411
  import { setTimeout as delay3 } from "node:timers/promises";
40263
40412
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
40264
40413
  function initializeTimeoutMs() {
@@ -40420,7 +40569,7 @@ class GodotLSPClient {
40420
40569
  if (this.initialized) {
40421
40570
  return;
40422
40571
  }
40423
- const rootPath = this.rootPath ?? dirname15(resolve5(filePath));
40572
+ const rootPath = this.rootPath ?? dirname16(resolve5(filePath));
40424
40573
  await this.initialize(rootPath);
40425
40574
  }
40426
40575
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -40921,7 +41070,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
40921
41070
  } catch {
40922
41071
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
40923
41072
  }
40924
- if (!existsSync17(join22(projectPath, "project.godot"))) {
41073
+ if (!existsSync18(join23(projectPath, "project.godot"))) {
40925
41074
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
40926
41075
  }
40927
41076
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -41361,8 +41510,8 @@ var init_prompts = __esm(() => {
41361
41510
  });
41362
41511
 
41363
41512
  // src/rename.ts
41364
- import { existsSync as existsSync18, mkdirSync as mkdirSync12, readFileSync as readFileSync19, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync13 } from "node:fs";
41365
- import { dirname as dirname16, posix as posix2 } from "node:path";
41513
+ import { existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync20, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "node:fs";
41514
+ import { dirname as dirname17, posix as posix2 } from "node:path";
41366
41515
 
41367
41516
  class Script {
41368
41517
  path;
@@ -42063,26 +42212,26 @@ function rewritesOf(plan, texts, move) {
42063
42212
  }
42064
42213
  function applyRewrites(rewrites) {
42065
42214
  for (const rewrite of rewrites) {
42066
- const now = readFileSync19(rewrite.absolute, "utf8");
42215
+ const now = readFileSync20(rewrite.absolute, "utf8");
42067
42216
  if (now !== rewrite.before) {
42068
42217
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
42069
42218
  }
42070
- if (rewrite.writeTo !== rewrite.absolute && existsSync18(rewrite.writeTo)) {
42219
+ if (rewrite.writeTo !== rewrite.absolute && existsSync19(rewrite.writeTo)) {
42071
42220
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
42072
42221
  }
42073
42222
  }
42074
42223
  const done = [];
42075
42224
  try {
42076
42225
  for (const rewrite of rewrites) {
42077
- mkdirSync12(dirname16(rewrite.writeTo), { recursive: true });
42226
+ mkdirSync12(dirname17(rewrite.writeTo), { recursive: true });
42078
42227
  const staging = `${rewrite.writeTo}.gdharness-rename`;
42079
42228
  writeFileSync13(staging, rewrite.after, "utf8");
42080
- renameSync2(staging, rewrite.writeTo);
42229
+ renameSync3(staging, rewrite.writeTo);
42081
42230
  let movedUid = false;
42082
42231
  if (rewrite.writeTo !== rewrite.absolute) {
42083
- unlinkSync2(rewrite.absolute);
42084
- if (existsSync18(`${rewrite.absolute}.uid`)) {
42085
- renameSync2(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
42232
+ unlinkSync3(rewrite.absolute);
42233
+ if (existsSync19(`${rewrite.absolute}.uid`)) {
42234
+ renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
42086
42235
  movedUid = true;
42087
42236
  }
42088
42237
  }
@@ -42093,9 +42242,9 @@ function applyRewrites(rewrites) {
42093
42242
  try {
42094
42243
  writeFileSync13(rewrite.absolute, rewrite.before, "utf8");
42095
42244
  if (rewrite.writeTo !== rewrite.absolute) {
42096
- unlinkSync2(rewrite.writeTo);
42245
+ unlinkSync3(rewrite.writeTo);
42097
42246
  if (movedUid) {
42098
- renameSync2(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
42247
+ renameSync3(`${rewrite.writeTo}.uid`, `${rewrite.absolute}.uid`);
42099
42248
  }
42100
42249
  }
42101
42250
  } catch {}
@@ -42127,8 +42276,8 @@ var init_rename = __esm(() => {
42127
42276
  });
42128
42277
 
42129
42278
  // src/rename-symbol.ts
42130
- import { existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync9, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
42131
- import { basename as basename5, dirname as dirname17, join as join23, relative as relative4, resolve as resolve6 } from "node:path";
42279
+ import { existsSync as existsSync20, mkdirSync as mkdirSync13, readdirSync as readdirSync10, realpathSync as realpathSync5, rmSync as rmSync10, writeFileSync as writeFileSync14 } from "node:fs";
42280
+ import { basename as basename6, dirname as dirname18, join as join24, relative as relative4, resolve as resolve6 } from "node:path";
42132
42281
  function resourcePathOf(projectPath, absolute) {
42133
42282
  const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
42134
42283
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -42141,13 +42290,13 @@ function onDiskSpelling(path) {
42141
42290
  let at = resolve6(path);
42142
42291
  for (;; ) {
42143
42292
  try {
42144
- return join23(realpathSync5.native(at), ...missing.reverse());
42293
+ return join24(realpathSync5.native(at), ...missing.reverse());
42145
42294
  } catch {
42146
- const parent = dirname17(at);
42295
+ const parent = dirname18(at);
42147
42296
  if (parent === at) {
42148
42297
  return resolve6(path);
42149
42298
  }
42150
- missing.push(basename5(at));
42299
+ missing.push(basename6(at));
42151
42300
  at = parent;
42152
42301
  }
42153
42302
  }
@@ -42194,7 +42343,7 @@ async function renameSymbol(request, services) {
42194
42343
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
42195
42344
  };
42196
42345
  }
42197
- if (existsSync19(request.newScriptPath)) {
42346
+ if (existsSync20(request.newScriptPath)) {
42198
42347
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
42199
42348
  }
42200
42349
  }
@@ -42370,7 +42519,7 @@ async function renameSymbol(request, services) {
42370
42519
  scripts,
42371
42520
  from: declaringPath,
42372
42521
  to: moveTo,
42373
- exists: (path) => existsSync19(`${projectPath}/${path.slice("res://".length)}`)
42522
+ exists: (path) => existsSync20(`${projectPath}/${path.slice("res://".length)}`)
42374
42523
  }, plan);
42375
42524
  }
42376
42525
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? null : declaringPath);
@@ -42569,15 +42718,15 @@ ${mention.kind}`;
42569
42718
  };
42570
42719
  }
42571
42720
  function writeLeftAloneDetail(projectPath, symbol, detail) {
42572
- const directory = join23(projectPath, ".godot", "gdharness-renames");
42721
+ const directory = join24(projectPath, ".godot", "gdharness-renames");
42573
42722
  mkdirSync13(directory, { recursive: true });
42574
42723
  detailWrites += 1;
42575
- const path = join23(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
42724
+ const path = join24(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
42576
42725
  writeFileSync14(path, `${JSON.stringify(detail, null, 2)}
42577
42726
  `, "utf8");
42578
- const kept = readdirSync9(directory).filter((name) => name.endsWith(".json")).sort().reverse();
42727
+ const kept = readdirSync10(directory).filter((name) => name.endsWith(".json")).sort().reverse();
42579
42728
  for (const old of kept.slice(10)) {
42580
- rmSync10(join23(directory, old), { force: true });
42729
+ rmSync10(join24(directory, old), { force: true });
42581
42730
  }
42582
42731
  return path;
42583
42732
  }
@@ -42617,15 +42766,15 @@ var init_rename_symbol = __esm(() => {
42617
42766
  });
42618
42767
 
42619
42768
  // src/restart-note.ts
42620
- import { mkdirSync as mkdirSync14, readFileSync as readFileSync20, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
42621
- import { dirname as dirname18, join as join24 } from "node:path";
42769
+ import { mkdirSync as mkdirSync14, readFileSync as readFileSync21, rmSync as rmSync11, writeFileSync as writeFileSync15 } from "node:fs";
42770
+ import { dirname as dirname19, join as join25 } from "node:path";
42622
42771
  function restartNotePath(projectPath) {
42623
- return join24(projectPath, ".godot", "gdharness-restart.json");
42772
+ return join25(projectPath, ".godot", "gdharness-restart.json");
42624
42773
  }
42625
42774
  function noteRestartBegun(note) {
42626
42775
  const path = restartNotePath(note.projectPath);
42627
42776
  try {
42628
- mkdirSync14(dirname18(path), { recursive: true });
42777
+ mkdirSync14(dirname19(path), { recursive: true });
42629
42778
  writeFileSync15(path, `${JSON.stringify(note, null, 2)}
42630
42779
  `, "utf8");
42631
42780
  } catch (error) {
@@ -42634,7 +42783,7 @@ function noteRestartBegun(note) {
42634
42783
  }
42635
42784
  function restartOwed(projectPath) {
42636
42785
  try {
42637
- const parsed = JSON.parse(readFileSync20(restartNotePath(projectPath), "utf8"));
42786
+ const parsed = JSON.parse(readFileSync21(restartNotePath(projectPath), "utf8"));
42638
42787
  if (typeof parsed !== "object" || parsed === null) {
42639
42788
  return null;
42640
42789
  }
@@ -42659,7 +42808,7 @@ var init_restart_note = __esm(() => {
42659
42808
  });
42660
42809
 
42661
42810
  // src/whole-file.ts
42662
- import { renameSync as renameSync3, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "node:fs";
42811
+ import { renameSync as renameSync4, rmSync as rmSync12, writeFileSync as writeFileSync16 } from "node:fs";
42663
42812
  function writeWhole(path, text) {
42664
42813
  const partial = `${path}.${process.pid}.partial`;
42665
42814
  try {
@@ -42670,7 +42819,7 @@ function writeWhole(path, text) {
42670
42819
  const until = Date.now() + HELD_FILE_MS;
42671
42820
  for (;; ) {
42672
42821
  try {
42673
- renameSync3(partial, path);
42822
+ renameSync4(partial, path);
42674
42823
  return true;
42675
42824
  } catch (error) {
42676
42825
  const code = error.code ?? "";
@@ -42692,24 +42841,24 @@ var init_whole_file = __esm(() => {
42692
42841
  // src/run-record.ts
42693
42842
  import { execFile as execFile4 } from "node:child_process";
42694
42843
  import { createHash as createHash4 } from "node:crypto";
42695
- import { mkdirSync as mkdirSync15, openSync as openSync2, readdirSync as readdirSync10, readFileSync as readFileSync21, realpathSync as realpathSync6, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
42696
- import { basename as basename6, dirname as dirname19, join as join25, resolve as resolve7 } from "node:path";
42844
+ import { mkdirSync as mkdirSync15, openSync as openSync2, readdirSync as readdirSync11, readFileSync as readFileSync22, realpathSync as realpathSync6, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
42845
+ import { basename as basename7, dirname as dirname20, join as join26, resolve as resolve7 } from "node:path";
42697
42846
  import { promisify as promisify4 } from "node:util";
42698
42847
  function runsDirectory() {
42699
- return join25(runtimeDirectory(), "runs");
42848
+ return join26(runtimeDirectory(), "runs");
42700
42849
  }
42701
42850
  function realAsFarAsItExists(path) {
42702
42851
  const missing = [];
42703
42852
  let at = path;
42704
42853
  for (;; ) {
42705
42854
  try {
42706
- return join25(realpathSync6.native(at), ...missing);
42855
+ return join26(realpathSync6.native(at), ...missing);
42707
42856
  } catch {
42708
- const parent = dirname19(at);
42857
+ const parent = dirname20(at);
42709
42858
  if (parent === at) {
42710
42859
  return path;
42711
42860
  }
42712
- missing.unshift(basename6(at));
42861
+ missing.unshift(basename7(at));
42713
42862
  at = parent;
42714
42863
  }
42715
42864
  }
@@ -42720,10 +42869,10 @@ function projectKey(projectPath) {
42720
42869
  return createHash4("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
42721
42870
  }
42722
42871
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
42723
- return join25(directory, "runs", `note-${projectKey(projectPath)}.json`);
42872
+ return join26(directory, "runs", `note-${projectKey(projectPath)}.json`);
42724
42873
  }
42725
42874
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
42726
- return join25(directory, "runs", `latest-${projectKey(servedBy)}.json`);
42875
+ return join26(directory, "runs", `latest-${projectKey(servedBy)}.json`);
42727
42876
  }
42728
42877
  function writeLatestRun(servedBy, latest) {
42729
42878
  try {
@@ -42733,7 +42882,7 @@ function writeLatestRun(servedBy, latest) {
42733
42882
  }
42734
42883
  function readLatestRun(servedBy, directory = runtimeDirectory()) {
42735
42884
  try {
42736
- const fields = JSON.parse(readFileSync21(latestRunPath(servedBy, directory), "utf8"));
42885
+ const fields = JSON.parse(readFileSync22(latestRunPath(servedBy, directory), "utf8"));
42737
42886
  if (typeof fields !== "object" || fields === null) {
42738
42887
  return null;
42739
42888
  }
@@ -42748,17 +42897,17 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
42748
42897
  }
42749
42898
  function openTranscript(startedAt) {
42750
42899
  mkdirSync15(runsDirectory(), { recursive: true });
42751
- const path = join25(runsDirectory(), `run-${startedAt}.log`);
42900
+ const path = join26(runsDirectory(), `run-${startedAt}.log`);
42752
42901
  return { path, fd: openSync2(path, "a") };
42753
42902
  }
42754
42903
  function profilePath(startedAt) {
42755
- return join25(runsDirectory(), `run-${startedAt}.profile.json`);
42904
+ return join26(runsDirectory(), `run-${startedAt}.profile.json`);
42756
42905
  }
42757
42906
  function noteIsAbout(note, playing) {
42758
42907
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
42759
42908
  }
42760
42909
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
42761
- return join25(directory, "runs", `editor-${projectKey(projectPath)}.json`);
42910
+ return join26(directory, "runs", `editor-${projectKey(projectPath)}.json`);
42762
42911
  }
42763
42912
  function writeEditorRunNote(note) {
42764
42913
  mkdirSync15(runsDirectory(), { recursive: true });
@@ -42769,7 +42918,7 @@ function writeEditorRunNote(note) {
42769
42918
  }
42770
42919
  function readEditorRunNote(projectPath) {
42771
42920
  for (const directory of runtimeDirectories()) {
42772
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join25(directory, "runs", "editor-run.json"));
42921
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join26(directory, "runs", "editor-run.json"));
42773
42922
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
42774
42923
  return note;
42775
42924
  }
@@ -42779,7 +42928,7 @@ function readEditorRunNote(projectPath) {
42779
42928
  function editorNoteAt(path) {
42780
42929
  let parsed;
42781
42930
  try {
42782
- parsed = JSON.parse(readFileSync21(path, "utf8"));
42931
+ parsed = JSON.parse(readFileSync22(path, "utf8"));
42783
42932
  } catch {
42784
42933
  return null;
42785
42934
  }
@@ -42803,7 +42952,7 @@ function readRunRecord(projectPath) {
42803
42952
  if (own !== null) {
42804
42953
  return own;
42805
42954
  }
42806
- const shared = recordAt(join25(directory, "runs", SHARED_NOTE));
42955
+ const shared = recordAt(join26(directory, "runs", SHARED_NOTE));
42807
42956
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
42808
42957
  return shared;
42809
42958
  }
@@ -42815,13 +42964,13 @@ function everyRunRecord() {
42815
42964
  for (const directory of runtimeDirectories()) {
42816
42965
  let entries;
42817
42966
  try {
42818
- entries = readdirSync10(join25(directory, "runs"));
42967
+ entries = readdirSync11(join26(directory, "runs"));
42819
42968
  } catch {
42820
42969
  continue;
42821
42970
  }
42822
42971
  for (const entry of entries) {
42823
42972
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
42824
- const record = recordAt(join25(directory, "runs", entry));
42973
+ const record = recordAt(join26(directory, "runs", entry));
42825
42974
  if (record !== null) {
42826
42975
  found.push(record);
42827
42976
  }
@@ -42833,7 +42982,7 @@ function everyRunRecord() {
42833
42982
  function recordAt(path) {
42834
42983
  let parsed;
42835
42984
  try {
42836
- parsed = JSON.parse(readFileSync21(path, "utf8"));
42985
+ parsed = JSON.parse(readFileSync22(path, "utf8"));
42837
42986
  } catch {
42838
42987
  return null;
42839
42988
  }
@@ -42886,7 +43035,7 @@ async function runningAs(pid) {
42886
43035
  const began = await startedAt(pid, null);
42887
43036
  const when = began === null ? {} : { startedAt: began };
42888
43037
  if (process.platform === "linux") {
42889
- const raw = readFileSync21(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
43038
+ const raw = readFileSync22(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
42890
43039
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
42891
43040
  }
42892
43041
  const args = (await execFileAsync("ps", ["-p", String(pid), "-o", "args="], { encoding: "utf8", timeout: 15000 })).stdout.trim();
@@ -42992,7 +43141,7 @@ function whyNotTheRun(record, running, needed) {
42992
43141
  if (after > SAME_RUN_WINDOW_MS) {
42993
43142
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
42994
43143
  }
42995
- const engine = record.command === undefined ? null : basename6(record.command);
43144
+ const engine = record.command === undefined ? null : basename7(record.command);
42996
43145
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
42997
43146
  const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
42998
43147
  if (wanted !== null && !said.includes(wanted)) {
@@ -43031,7 +43180,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
43031
43180
  }
43032
43181
  function clearRunRecord(projectPath) {
43033
43182
  for (const directory of runtimeDirectories()) {
43034
- const shared = join25(directory, "runs", SHARED_NOTE);
43183
+ const shared = join26(directory, "runs", SHARED_NOTE);
43035
43184
  const sharedRecord = recordAt(shared);
43036
43185
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
43037
43186
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
@@ -43056,7 +43205,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
43056
43205
  ]));
43057
43206
  let entries;
43058
43207
  try {
43059
- entries = readdirSync10(directory);
43208
+ entries = readdirSync11(directory);
43060
43209
  } catch {
43061
43210
  return;
43062
43211
  }
@@ -43065,7 +43214,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
43065
43214
  if (!transcript && !entry.endsWith(".partial")) {
43066
43215
  continue;
43067
43216
  }
43068
- const path = join25(directory, entry);
43217
+ const path = join26(directory, entry);
43069
43218
  if (kept.has(path) || ageMs(path, now) < keepMs) {
43070
43219
  continue;
43071
43220
  }
@@ -43086,7 +43235,7 @@ var init_run_record = __esm(() => {
43086
43235
  });
43087
43236
 
43088
43237
  // src/script-origins.ts
43089
- import { join as join26 } from "node:path";
43238
+ import { join as join27 } from "node:path";
43090
43239
  function unresolvedClassIn(message) {
43091
43240
  return UNRESOLVED.exec(message)?.[1] ?? null;
43092
43241
  }
@@ -43139,7 +43288,7 @@ function checkWithEngine(godotPath, projectPath) {
43139
43288
  const ran = await runEngine(godotPath, [
43140
43289
  "--headless",
43141
43290
  "--log-file",
43142
- join26(logDir, "engine.log"),
43291
+ join27(logDir, "engine.log"),
43143
43292
  "--path",
43144
43293
  projectPath,
43145
43294
  "--check-only",
@@ -43183,18 +43332,11 @@ var init_script_origins = __esm(() => {
43183
43332
  RELOADED_AT = /GDScript::reload \((res:\/\/.+):(\d+)\)$/;
43184
43333
  });
43185
43334
 
43186
- // src/godot-variant.ts
43187
- var FLAG_64, MOST_MESSAGE_BYTES;
43188
- var init_godot_variant = __esm(() => {
43189
- FLAG_64 = 1 << 16;
43190
- MOST_MESSAGE_BYTES = (8 << 20) + 4;
43191
- });
43192
-
43193
43335
  // src/script-profile.ts
43194
- import { readFileSync as readFileSync22 } from "node:fs";
43336
+ import { readFileSync as readFileSync23 } from "node:fs";
43195
43337
  function readProfileFile(path) {
43196
43338
  try {
43197
- const parsed = JSON.parse(readFileSync22(path, "utf8"));
43339
+ const parsed = JSON.parse(readFileSync23(path, "utf8"));
43198
43340
  return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
43199
43341
  } catch {
43200
43342
  return null;
@@ -43356,7 +43498,7 @@ function serverInstructions() {
43356
43498
  "Call editor_status first: it says whether an editor is connected and whether its addon matches this server.",
43357
43499
  "Start the game with editor_run start, never by spawning an engine, and read editor_output after every run:",
43358
43500
  "its clean verdict and its error entries are how a run is judged, and editor_run wait is how to wait for one.",
43359
- "The runtime_* tools work on the running game and the debug_* tools on the editor's debugger.",
43501
+ "The runtime_* tools work on the running game and the debug_* tools on its debugger.",
43360
43502
  "A refusal names what would have worked, so read it before calling again."
43361
43503
  ].join(" ");
43362
43504
  }
@@ -43372,17 +43514,17 @@ import {
43372
43514
  closeSync as closeSync2,
43373
43515
  constants,
43374
43516
  copyFileSync,
43375
- existsSync as existsSync20,
43517
+ existsSync as existsSync21,
43376
43518
  fstatSync,
43377
43519
  mkdirSync as mkdirSync16,
43378
43520
  openSync as openSync3,
43379
- readdirSync as readdirSync11,
43380
- readFileSync as readFileSync23,
43521
+ readdirSync as readdirSync12,
43522
+ readFileSync as readFileSync24,
43381
43523
  readSync as readSync2,
43382
43524
  rmdirSync as rmdirSync2,
43383
43525
  statSync as statSync10
43384
43526
  } from "node:fs";
43385
- import { basename as basename7, dirname as dirname20, extname as extname3, isAbsolute as isAbsolute3, join as join27, normalize as normalize2, relative as relative5 } from "node:path";
43527
+ import { basename as basename8, dirname as dirname21, extname as extname3, isAbsolute as isAbsolute4, join as join28, normalize as normalize2, relative as relative5 } from "node:path";
43386
43528
  import { setTimeout as delay4 } from "node:timers/promises";
43387
43529
  import { fileURLToPath as fileURLToPath5 } from "node:url";
43388
43530
  import { promisify as promisify5 } from "node:util";
@@ -43413,22 +43555,22 @@ function writtenFileRefusal(path, rule) {
43413
43555
  if (/^(res|user):\/\//.test(path)) {
43414
43556
  return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
43415
43557
  }
43416
- if (!isAbsolute3(path)) {
43558
+ if (!isAbsolute4(path)) {
43417
43559
  return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
43418
43560
  }
43419
43561
  if (!rule.extensions.includes(extname3(path).toLowerCase())) {
43420
43562
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
43421
43563
  }
43422
- const directory = realPathOr(dirname20(path));
43423
- if (!existsSync20(directory)) {
43424
- return `${named}'s directory ${dirname20(path)} does not exist; ${what} creates no directories.`;
43564
+ const directory = realPathOr(dirname21(path));
43565
+ if (!existsSync21(directory)) {
43566
+ return `${named}'s directory ${dirname21(path)} does not exist; ${what} creates no directories.`;
43425
43567
  }
43426
- const where = join27(directory, basename7(path));
43568
+ const where = join28(directory, basename8(path));
43427
43569
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
43428
43570
  if (inside !== undefined) {
43429
43571
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
43430
43572
  }
43431
- const taken = rule.writes(where).find((file) => existsSync20(file));
43573
+ const taken = rule.writes(where).find((file) => existsSync21(file));
43432
43574
  if (taken !== undefined) {
43433
43575
  return taken === where ? `${named} ${path} already exists; ${what} is not written over a file. Name a new one.` : `${named} ${path} would write ${taken}, which already exists; ${what} is not written over a file. Name a new one.`;
43434
43576
  }
@@ -43486,6 +43628,30 @@ function longerWaitNote(tool, op, waitedMs) {
43486
43628
  const how = tool === "runtime_wait" && op === "frames" ? `the wait is counted from the number of frames, at ${SLOWEST_FRAME_RATE} a second on top of GDHARNESS_RUNTIME_TIMEOUT_MS: a game drawing frames more slowly than that is waited on in fewer frames at a time, and a game inside one long frame, such as a long _ready, answers no call however few frames are asked for` : "the wait is GDHARNESS_RUNTIME_TIMEOUT_MS, which is set in the environment the server is started with";
43487
43629
  return `${tool} ${op} takes no timeoutMs: it was given ${waitedMs}ms, and ${how}.`;
43488
43630
  }
43631
+ function keptGameRequest(tool, op, args) {
43632
+ if (tool === "debug_state") {
43633
+ if (op === "output") {
43634
+ return { op: "output" };
43635
+ }
43636
+ if (op === "variables") {
43637
+ const frame = args["frameId"];
43638
+ return { op: "variables", frame: typeof frame === "number" ? frame : 0 };
43639
+ }
43640
+ return { op: "stack" };
43641
+ }
43642
+ switch (op) {
43643
+ case "pause":
43644
+ return { op: "pause" };
43645
+ case "step_over":
43646
+ return { op: "resume", how: "next" };
43647
+ case "step_into":
43648
+ return { op: "resume", how: "step" };
43649
+ case "step_out":
43650
+ return { op: "resume", how: "out" };
43651
+ default:
43652
+ return { op: "resume", how: "continue" };
43653
+ }
43654
+ }
43489
43655
  function projectSpelling(projectPath, absolutePath) {
43490
43656
  return `res://${relative5(realPathOr(projectPath), realPathOr(absolutePath)).replaceAll("\\", "/")}`;
43491
43657
  }
@@ -43900,6 +44066,7 @@ function notRunNote(notRun, failFast) {
43900
44066
  function exportAnswer(ending, asked, before, after) {
43901
44067
  const { log } = ending;
43902
44068
  const written = after !== null && after !== before;
44069
+ const notCopied = librariesNotCopied(log.all);
43903
44070
  const verdict = {
43904
44071
  exported: ending.exitCode === 0 && ending.failure === null && log.count("error") === 0 && written,
43905
44072
  ...asked,
@@ -43908,6 +44075,10 @@ function exportAnswer(ending, asked, before, after) {
43908
44075
  failure: ending.failure ?? undefined,
43909
44076
  errors: log.count("error"),
43910
44077
  warnings: log.count("warning"),
44078
+ ...notCopied.length === 0 ? {} : {
44079
+ librariesNotLoaded: notCopied,
44080
+ extensionNote: `The export could not load ${notCopied.join(" and ")}: on Windows an engine running as the editor, which an export is, loads a GDExtension from a ~ copy beside the library, and another engine on this project held that copy: it could not be moved aside before the export, or another engine made a new one in between. Export again once that engine has loaded or exited.`
44081
+ },
43911
44082
  ...foldedEntriesAnswer(log)
43912
44083
  };
43913
44084
  if (verdict.exported) {
@@ -43945,7 +44116,7 @@ function engineFlagsNote(args) {
43945
44116
  return `${named} ${among.length === 1 ? "is a flag" : "are flags"} the engine reads, and args goes to the game after --, where the engine does not read ${among.length === 1 ? "it" : "them"}. engineArgs hands the engine its own flags; ${among.length === 1 ? "leave it" : "leave them"} in args only for a game that reads ${among.length === 1 ? "that flag" : "those flags"} itself.`;
43946
44117
  }
43947
44118
  function editorAddonMissing(projectPath) {
43948
- if (!existsSync20(join27(projectPath, "project.godot"))) {
44119
+ if (!existsSync21(join28(projectPath, "project.godot"))) {
43949
44120
  return null;
43950
44121
  }
43951
44122
  const report = inspectProject(projectPath);
@@ -44181,7 +44352,7 @@ function profileFrameFunctions(projectPath) {
44181
44352
  let projectGodot = null;
44182
44353
  if (projectPath !== null) {
44183
44354
  try {
44184
- projectGodot = readFileSync23(join27(projectPath, "project.godot"), "utf8");
44355
+ projectGodot = readFileSync24(join28(projectPath, "project.godot"), "utf8");
44185
44356
  } catch {}
44186
44357
  }
44187
44358
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -44205,7 +44376,7 @@ function projectSourceOf(projectPath) {
44205
44376
  let source = null;
44206
44377
  if (inside.ok) {
44207
44378
  try {
44208
- source = readFileSync23(inside.absolutePath, "utf8");
44379
+ source = readFileSync24(inside.absolutePath, "utf8");
44209
44380
  } catch {}
44210
44381
  }
44211
44382
  read.set(script, source);
@@ -44355,7 +44526,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
44355
44526
  }
44356
44527
  function gdUnitFailsOnScriptErrors(projectPath) {
44357
44528
  try {
44358
- return settingKeys(readFileSync23(join27(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
44529
+ return settingKeys(readFileSync24(join28(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
44359
44530
  } catch {
44360
44531
  return true;
44361
44532
  }
@@ -44370,13 +44541,13 @@ function uncaughtNote(uncaught) {
44370
44541
  }
44371
44542
  function elsewhereIn(projectPath, asked) {
44372
44543
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
44373
- if (wanted === "" || existsSync20(join27(projectPath, wanted))) {
44544
+ if (wanted === "" || existsSync21(join28(projectPath, wanted))) {
44374
44545
  return "";
44375
44546
  }
44376
44547
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
44377
44548
  let near = [];
44378
44549
  try {
44379
- near = readdirSync11(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
44550
+ near = readdirSync12(projectPath, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.toLowerCase() !== root).map((entry) => entry.name).filter((name) => name.toLowerCase().startsWith("test") || root.startsWith(name.toLowerCase()));
44380
44551
  } catch {
44381
44552
  return "";
44382
44553
  }
@@ -44466,17 +44637,17 @@ function isDirectory(path) {
44466
44637
  }
44467
44638
  }
44468
44639
  function scriptsUnder(directory, prefix) {
44469
- if (existsSync20(join27(directory, ".gdignore"))) {
44640
+ if (existsSync21(join28(directory, ".gdignore"))) {
44470
44641
  return [];
44471
44642
  }
44472
44643
  const found = [];
44473
- for (const entry of readdirSync11(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
44644
+ for (const entry of readdirSync12(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
44474
44645
  if (entry.name.startsWith(".")) {
44475
44646
  continue;
44476
44647
  }
44477
44648
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
44478
44649
  if (entry.isDirectory()) {
44479
- found.push(...scriptsUnder(join27(directory, entry.name), path));
44650
+ found.push(...scriptsUnder(join28(directory, entry.name), path));
44480
44651
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
44481
44652
  found.push(path);
44482
44653
  }
@@ -44484,7 +44655,7 @@ function scriptsUnder(directory, prefix) {
44484
44655
  return found;
44485
44656
  }
44486
44657
  function hasMainScene(projectFile) {
44487
- const scene = parseProjectGodot(readFileSync23(projectFile, "utf8"))["application"]?.["run/main_scene"];
44658
+ const scene = parseProjectGodot(readFileSync24(projectFile, "utf8"))["application"]?.["run/main_scene"];
44488
44659
  return typeof scene === "string" && scene !== "";
44489
44660
  }
44490
44661
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -44492,13 +44663,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
44492
44663
  return false;
44493
44664
  }
44494
44665
  try {
44495
- return readFileSync23(join27(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
44666
+ return readFileSync24(join28(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
44496
44667
  } catch {
44497
44668
  return false;
44498
44669
  }
44499
44670
  }
44500
44671
  function editorPlaysHeadless(projectFile) {
44501
- const runArgs = parseProjectGodot(readFileSync23(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
44672
+ const runArgs = parseProjectGodot(readFileSync24(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
44502
44673
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
44503
44674
  }
44504
44675
  function camelCased(params) {
@@ -44517,7 +44688,7 @@ function needsItsOwnProcess(asked) {
44517
44688
  class GodotServer {
44518
44689
  stdio = null;
44519
44690
  locator = new GodotLocator;
44520
- operationsScript = join27(__dirname2, "godot", "operations", "godot_operations.gd");
44691
+ operationsScript = join28(__dirname2, "godot", "operations", "godot_operations.gd");
44521
44692
  godotBridge;
44522
44693
  tools = buildToolDefinitions();
44523
44694
  activeProcess = null;
@@ -44542,6 +44713,7 @@ class GodotServer {
44542
44713
  launchedDropped = null;
44543
44714
  closedHere = null;
44544
44715
  inFlight = new Map;
44716
+ callsOnGames = new Set;
44545
44717
  callsTaken = 0;
44546
44718
  clientNoted = null;
44547
44719
  parentCommand = null;
@@ -44773,14 +44945,14 @@ class GodotServer {
44773
44945
  }
44774
44946
  noteTheStart() {
44775
44947
  const cwd = process.cwd();
44776
- const mcpJson = join27(cwd, ".mcp.json");
44948
+ const mcpJson = join28(cwd, ".mcp.json");
44777
44949
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
44778
44950
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
44779
44951
  writeLifecycle("started", {
44780
44952
  argv: process.argv.slice(1),
44781
44953
  cwd,
44782
44954
  project: this.ownProject,
44783
- mcpJson: existsSync20(mcpJson) ? mcpJson : null
44955
+ mcpJson: existsSync21(mcpJson) ? mcpJson : null
44784
44956
  });
44785
44957
  if (predecessor !== null) {
44786
44958
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -44921,7 +45093,7 @@ class GodotServer {
44921
45093
  return checked.response;
44922
45094
  }
44923
45095
  const named = args["projectPath"];
44924
- if (typeof named === "string" && existsSync20(join27(named, "project.godot"))) {
45096
+ if (typeof named === "string" && existsSync21(join28(named, "project.godot"))) {
44925
45097
  this.lastProjectPath = named;
44926
45098
  }
44927
45099
  this.updates.refresh();
@@ -45392,28 +45564,70 @@ class GodotServer {
45392
45564
  return this.createErrorResponse(located.reason, PATH_SOLUTIONS);
45393
45565
  }
45394
45566
  const session = this.dapHoldingBreakpointsOf(project.value.path);
45395
- const answered = await this.handleDAP(op === "set" ? "dap_set_breakpoint" : "dap_remove_breakpoint", {
45396
- ...args,
45397
- scriptPath: realPathOr(located.absolutePath)
45398
- });
45399
- if (answered.isError === true) {
45400
- return answered;
45567
+ const scriptPath = realPathOr(located.absolutePath);
45568
+ const line = readPositiveNumber(args, "line");
45569
+ if (line === undefined) {
45570
+ return this.createErrorResponse("line is required: the one-based line to stop on.");
45571
+ }
45572
+ const editor = this.godotBridge.isConnected();
45573
+ let answer;
45574
+ if (editor) {
45575
+ const answered = await this.handleDAP(op === "set" ? "dap_set_breakpoint" : "dap_remove_breakpoint", {
45576
+ ...args,
45577
+ scriptPath
45578
+ });
45579
+ if (answered.isError === true) {
45580
+ return answered;
45581
+ }
45582
+ answer = asParams(JSON.parse(answered.content[0]?.text ?? "{}"));
45583
+ } else {
45584
+ if (op === "set") {
45585
+ session.holdBreakpoints([{ scriptPath, lines: [line] }]);
45586
+ } else {
45587
+ session.forgetBreakpoint(scriptPath, line);
45588
+ }
45589
+ answer = { [op === "set" ? "set" : "removed"]: true };
45401
45590
  }
45402
45591
  const held = session.breakpointsHeld();
45403
45592
  writeBreakpointNote(project.value.path, held);
45593
+ const kept = this.keptRun();
45594
+ const told = kept === null ? null : await askTheKeepersDebugger(kept.keeper, {
45595
+ op: "breakpoint",
45596
+ script: projectSpelling(project.value.path, scriptPath),
45597
+ line,
45598
+ set: op === "set"
45599
+ }, KEEPER_DEBUG_MS);
45404
45600
  return this.jsonTextResponse({
45405
- ...asParams(JSON.parse(answered.content[0]?.text ?? "{}")),
45601
+ ...answer,
45406
45602
  held: this.breakpointsAsProjectSpells(project.value.path, held),
45407
- setInEditor: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsInEditor()),
45408
- note: "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, and kept in the project for the server after a reconnect.",
45409
- ...this.breakpointsAtRisk()
45603
+ ...editor ? {
45604
+ setInEditor: this.breakpointsAsProjectSpells(project.value.path, session.breakpointsInEditor())
45605
+ } : {},
45606
+ ...told === null ? {} : ("unreached" in told) || typeof told["refused"] === "string" ? {
45607
+ runningGame: `The game this server started is running, and its debugger was not told: ${String("unreached" in told ? told.unreached : told["refused"])}. It takes the line when it is started again.`
45608
+ } : {
45609
+ runningGame: `Told to the running game (pid ${kept?.pid ?? "?"}), which this server started.`
45610
+ },
45611
+ note: editor ? "The editor keeps a breakpoint set this way for one play. Every breakpoint held here is sent again before each play editor_run starts, handed to every game this server starts itself, and kept in the project for the server after a reconnect." : "No editor is connected, so the set under held is kept here: every game this server starts is handed it as it starts, an editor that connects is sent it before its first play, and it is kept in the project for the server after a reconnect.",
45612
+ ...editor ? this.breakpointsAtRisk() : {}
45410
45613
  });
45411
45614
  }
45412
45615
  case "debug_control":
45413
45616
  case "debug_state": {
45617
+ const kept = this.keptRun();
45618
+ if (kept !== null) {
45619
+ return await this.askTheKeptGame(kept.keeper, keptGameRequest(tool, op, args), kept.projectPath ?? "");
45620
+ }
45414
45621
  if (tool === "debug_state" && op === "output") {
45415
45622
  return await this.handleDAP("dap_get_output", args);
45416
45623
  }
45624
+ const played = this.activeProcess;
45625
+ if ((op === "pause" || op === "step_out") && played?.throughEditor === true && !exited(played)) {
45626
+ return this.createErrorResponse(op === "pause" ? "A game the editor plays cannot be paused from here: Godot's debug adapter answers a pause by reporting the game stopped and leaving it running." : "A game the editor plays cannot be stepped out of a function from here: Godot's debug adapter implements no stepOut.", [
45627
+ "Set a breakpoint where it should stop, then step over or into from there",
45628
+ "A game this server starts, as editor_run start does with args, savesIn or env, has a debugger here that pauses and steps out"
45629
+ ]);
45630
+ }
45417
45631
  const held = await this.debuggedGame(tool === "debug_control" ? "move" : "read");
45418
45632
  if (!held.ok) {
45419
45633
  return held.response;
@@ -45437,8 +45651,8 @@ class GodotServer {
45437
45651
  if (path === undefined) {
45438
45652
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
45439
45653
  }
45440
- const file = join27(path, "project.godot");
45441
- if (!existsSync20(file)) {
45654
+ const file = join28(path, "project.godot");
45655
+ if (!existsSync21(file)) {
45442
45656
  return {
45443
45657
  ok: false,
45444
45658
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -45484,7 +45698,7 @@ class GodotServer {
45484
45698
  }
45485
45699
  runEnvironment(args) {
45486
45700
  const savesIn = readNonEmptyString(args, "savesIn");
45487
- if (savesIn !== undefined && !isAbsolute3(savesIn)) {
45701
+ if (savesIn !== undefined && !isAbsolute4(savesIn)) {
45488
45702
  return {
45489
45703
  ok: false,
45490
45704
  response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
@@ -45557,8 +45771,8 @@ class GodotServer {
45557
45771
  if (!game.throughEditor) {
45558
45772
  return {
45559
45773
  ok: false,
45560
- response: this.createErrorResponse("The running game is its own process, so no debugger is holding it: breakpoints never hit and there is no stack to read.", [
45561
- "editor_run start plays it through the open editor, whose debugger the debug_* tools speak to",
45774
+ response: this.createErrorResponse("The running game has no debugger: it was started with none, or by a keeper that could not take questions, so breakpoints never hit and there is no stack to read.", [
45775
+ "editor_run start starts it again, and the keeper holding a game this server starts is its debugger",
45562
45776
  "editor_status says whether an editor has reached this server"
45563
45777
  ])
45564
45778
  };
@@ -46068,11 +46282,11 @@ class GodotServer {
46068
46282
  if (!engine.ok) {
46069
46283
  return engine.response;
46070
46284
  }
46071
- const application = parseProjectGodot(readFileSync23(project.value.file, "utf8"))["application"] ?? {};
46285
+ const application = parseProjectGodot(readFileSync24(project.value.file, "utf8"))["application"] ?? {};
46072
46286
  const name = application["config/name"];
46073
46287
  const mainScene = application["run/main_scene"];
46074
46288
  const info = {
46075
- name: typeof name === "string" && name !== "" ? name : basename7(project.value.path),
46289
+ name: typeof name === "string" && name !== "" ? name : basename8(project.value.path),
46076
46290
  path: project.value.path,
46077
46291
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
46078
46292
  godotVersion: await this.godotVersion(engine.value),
@@ -46124,18 +46338,18 @@ class GodotServer {
46124
46338
  if (!engine.ok) {
46125
46339
  return engine.response;
46126
46340
  }
46127
- const folder = dirname20(output.absolutePath);
46341
+ const folder = dirname21(output.absolutePath);
46128
46342
  try {
46129
46343
  mkdirSync16(folder, { recursive: true });
46130
46344
  } catch (error) {
46131
- return this.createErrorResponse(`Cannot create ${dirname20(output.relativePath)} to export into: ${errorMessage(error)}`);
46345
+ return this.createErrorResponse(`Cannot create ${dirname21(output.relativePath)} to export into: ${errorMessage(error)}`);
46132
46346
  }
46133
46347
  const debug = readBoolean(args, "debug") ?? false;
46134
46348
  const exportLogs = scratchDirectory("export");
46135
46349
  const exportArgs = [
46136
46350
  "--headless",
46137
46351
  "--log-file",
46138
- join27(exportLogs, "engine.log"),
46352
+ join28(exportLogs, "engine.log"),
46139
46353
  "--path",
46140
46354
  project.value.path,
46141
46355
  debug ? "--export-debug" : "--export-release",
@@ -46143,8 +46357,12 @@ class GodotServer {
46143
46357
  output.absolutePath
46144
46358
  ];
46145
46359
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
46146
- const writtenAt = () => existsSync20(output.absolutePath) ? statSync10(output.absolutePath).mtimeMs : null;
46360
+ const writtenAt = () => existsSync21(output.absolutePath) ? statSync10(output.absolutePath).mtimeMs : null;
46147
46361
  const before = writtenAt();
46362
+ const freed = freeLibraryCopies(project.value.path);
46363
+ if (freed.setAside.length > 0 || freed.swept.length > 0) {
46364
+ this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
46365
+ }
46148
46366
  let ending;
46149
46367
  try {
46150
46368
  ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
@@ -46207,10 +46425,10 @@ class GodotServer {
46207
46425
  if (movie !== null) {
46208
46426
  return this.createErrorResponse(movie);
46209
46427
  }
46210
- const missing = paths.value.filter((path) => !existsSync20(join27(project.value.path, path.slice("res://".length))));
46428
+ const missing = paths.value.filter((path) => !existsSync21(join28(project.value.path, path.slice("res://".length))));
46211
46429
  const asked = paths.value.filter((path) => !missing.includes(path));
46212
46430
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
46213
- const noRunner = !existsSync20(join27(project.value.path, runner));
46431
+ const noRunner = !existsSync21(join28(project.value.path, runner));
46214
46432
  if (asked.length === 0) {
46215
46433
  const verdict = `nothing at ${missing.join(" and ")}`;
46216
46434
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -46301,21 +46519,21 @@ class GodotServer {
46301
46519
  await untilGone([run.process.pid]);
46302
46520
  }
46303
46521
  await run.settled;
46304
- const reportsDir = join27(project.value.path, ".godot", ...ours.split("/"));
46522
+ const reportsDir = join28(project.value.path, ".godot", ...ours.split("/"));
46305
46523
  let report = null;
46306
46524
  let reportProblem = null;
46307
46525
  try {
46308
- const written = existsSync20(reportsDir) ? readdirSync11(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
46526
+ const written = existsSync21(reportsDir) ? readdirSync12(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
46309
46527
  const newest = written.at(-1);
46310
46528
  if (newest !== undefined) {
46311
- report = parseJUnit(readFileSync23(join27(reportsDir, newest, "results.xml"), "utf8"));
46529
+ report = parseJUnit(readFileSync24(join28(reportsDir, newest, "results.xml"), "utf8"));
46312
46530
  }
46313
46531
  } catch (error) {
46314
46532
  reportProblem = errorMessage(error);
46315
46533
  } finally {
46316
46534
  discard(reportsDir, userData);
46317
46535
  try {
46318
- rmdirSync2(dirname20(reportsDir));
46536
+ rmdirSync2(dirname21(reportsDir));
46319
46537
  } catch {}
46320
46538
  }
46321
46539
  const engineEntries = foldedRepeats(run.log.select({
@@ -46575,11 +46793,11 @@ class GodotServer {
46575
46793
  const cached = cachedClasses(projectPath);
46576
46794
  const sourceOf = (resourcePath) => {
46577
46795
  const contained = resolveWithinProject(projectPath, resourcePath);
46578
- if (!contained.ok || !existsSync20(contained.absolutePath)) {
46796
+ if (!contained.ok || !existsSync21(contained.absolutePath)) {
46579
46797
  return null;
46580
46798
  }
46581
46799
  try {
46582
- return readFileSync23(contained.absolutePath, "utf8");
46800
+ return readFileSync24(contained.absolutePath, "utf8");
46583
46801
  } catch {
46584
46802
  return null;
46585
46803
  }
@@ -46644,11 +46862,11 @@ class GodotServer {
46644
46862
  }
46645
46863
  sourceOfClass(projectPath, resourcePath) {
46646
46864
  const contained = resolveWithinProject(projectPath, resourcePath);
46647
- if (!contained.ok || !existsSync20(contained.absolutePath)) {
46865
+ if (!contained.ok || !existsSync21(contained.absolutePath)) {
46648
46866
  return null;
46649
46867
  }
46650
46868
  try {
46651
- return readFileSync23(contained.absolutePath, "utf8");
46869
+ return readFileSync24(contained.absolutePath, "utf8");
46652
46870
  } catch {
46653
46871
  return null;
46654
46872
  }
@@ -46769,6 +46987,38 @@ class GodotServer {
46769
46987
  breakpointsAtRisk: status.syncsBreakpoints === false ? "This editor clears every breakpoint in its script editor whenever a debug session opens on its adapter, which this server does for its first breakpoint or stack read: network/debug_adapter/sync_breakpoints is off. The installed addon turns it on as it loads, so the editor is running an older one; editor_launch restart puts the installed addon in, or turn the setting on in Editor Settings > Network > Debug Adapter." : "Whether this editor keeps its breakpoints when a debug session opens cannot be told: the addon it is running predates the report. Godot clears them unless network/debug_adapter/sync_breakpoints is on, which the installed addon turns on as it loads; editor_launch restart puts the installed addon in."
46770
46988
  };
46771
46989
  }
46990
+ breakpointsForAGame(projectPath) {
46991
+ return this.dapHoldingBreakpointsOf(projectPath).breakpointsHeld().flatMap((one) => one.lines.map((line) => `${projectSpelling(projectPath, one.scriptPath)}:${line}`));
46992
+ }
46993
+ keptRun() {
46994
+ const game = this.activeProcess;
46995
+ if (game === null || game.throughEditor || exited(game) || game.keeper === undefined) {
46996
+ return null;
46997
+ }
46998
+ return { ...game, keeper: game.keeper };
46999
+ }
47000
+ async askTheKeptGame(keeper, request, projectPath) {
47001
+ const answered = await askTheKeepersDebugger(keeper, request, KEEPER_DEBUG_MS);
47002
+ if ("unreached" in answered) {
47003
+ return this.createErrorResponse(`The debugger of the running game did not answer: ${String(answered.unreached)}.`, [
47004
+ "editor_run start starts the game again, with a debugger",
47005
+ "editor_status says whether it is still running"
47006
+ ]);
47007
+ }
47008
+ if (typeof answered["refused"] === "string") {
47009
+ return this.createErrorResponse(answered["refused"]);
47010
+ }
47011
+ const stack = answered["stack"];
47012
+ return this.jsonTextResponse({
47013
+ ...answered,
47014
+ ...Array.isArray(stack) ? {
47015
+ stack: stack.map((frame) => ({
47016
+ ...frame,
47017
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join28(projectPath, frame["script"].slice("res://".length)) } : {}
47018
+ }))
47019
+ } : {}
47020
+ });
47021
+ }
46772
47022
  breakpointsAsProjectSpells(projectPath, held) {
46773
47023
  return held.map((one) => ({
46774
47024
  scriptPath: projectSpelling(projectPath, one.scriptPath),
@@ -47034,7 +47284,7 @@ class GodotServer {
47034
47284
  return null;
47035
47285
  }
47036
47286
  try {
47037
- return settingKeys(readFileSync23(join27(projectPath, "project.godot"), "utf8"));
47287
+ return settingKeys(readFileSync24(join28(projectPath, "project.godot"), "utf8"));
47038
47288
  } catch {
47039
47289
  return null;
47040
47290
  }
@@ -47299,6 +47549,7 @@ class GodotServer {
47299
47549
  }
47300
47550
  async openAnEditor(engine, projectPath, ports, hidden) {
47301
47551
  clearEditorLog(projectPath);
47552
+ freeLibraryCopies(projectPath);
47302
47553
  const launched = await launchOutsideTheTree({
47303
47554
  command: engine,
47304
47555
  args: editorArguments(projectPath, ports, editorLogPath(projectPath), hidden),
@@ -47430,9 +47681,10 @@ class GodotServer {
47430
47681
  started.announcedBefore = alreadyPlaying;
47431
47682
  started.bootScene = runtimeWaitMs.scene;
47432
47683
  this.activeProcess = started;
47433
- const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ? " The editor cannot be handed arguments for a game it plays, so this one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? " The editor plays a game with its own environment, so a run given one was started " + "here: the debug_* tools answer only for a game the editor is playing." : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}: the debug_* tools answer only for a game the editor is playing, and silent: false plays it through the editor.` : "";
47684
+ const spawnedInstead = this.godotBridge.isConnected() && editorWouldPlay && (given.value.length > 0 || flags.value.length > 0) ? ` The editor cannot be handed arguments for a game it plays, so this one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && needsItsOwnProcess(asked.value) ? ` The editor plays a game with its own environment, so a run given one was started here.${OWN_DEBUGGER}` : this.godotBridge.isConnected() && editorWouldPlay && silent ? " The editor cannot play a game silent, which takes an engine flag, so this one was " + `started here${args["silent"] === true ? "" : ", silent because GDHARNESS_SILENT says so"}, and silent: false plays it through the editor.${OWN_DEBUGGER}` : "";
47434
47685
  const endedForThis = endedToStartThis(ended);
47435
47686
  const stranded = wereHereFirst.filter((one) => one.pid !== ended?.pid && alive(one.pid));
47687
+ const handed = this.dapHoldingBreakpointsOf(project.value.path).breakpointsHeld();
47436
47688
  const alsoRunning = leftRunningNote(stranded);
47437
47689
  return this.jsonTextResponse({
47438
47690
  started: true,
@@ -47442,6 +47694,7 @@ class GodotServer {
47442
47694
  arguments: cmdArgs,
47443
47695
  argsNote: engineFlagsNote(given.value),
47444
47696
  transcript: started.transcript,
47697
+ breakpoints: started.keeper === undefined || handed.length === 0 ? undefined : this.breakpointsAsProjectSpells(project.value.path, handed),
47445
47698
  profile: profileOnStart(profiled, "gdharness", runtimeAddonRegistered(project.value.path)),
47446
47699
  refreshedClasses: refreshed.value,
47447
47700
  ...scanWaitAnswer(scanned),
@@ -47450,12 +47703,12 @@ class GodotServer {
47450
47703
  previousRunLeft: previousRunLeft(ended),
47451
47704
  ...previousRunHow(ended),
47452
47705
  runtime: await this.runtimeUp(project.value.path, alreadyPlaying, runtimeWaitMs, given.value.length > 0),
47453
- message: `Use editor_output for what it prints and editor_run stop to end it.${spawnedInstead}${endedForThis}${alsoRunning}`
47706
+ message: `Use editor_output for what it prints and editor_run stop to end it.${started.keeper !== undefined && handed.length > 0 ? " It stops at the lines under breakpoints, and debug_state stack reads where it is held." : ""}${spawnedInstead}${endedForThis}${alsoRunning}`
47454
47707
  });
47455
47708
  }
47456
47709
  async runtimeUp(projectPath, before, budget, withArgs) {
47457
47710
  const budgetMs = budget.ms;
47458
- if (!existsSync20(join27(projectPath, RUNTIME_AUTOLOAD.path))) {
47711
+ if (!existsSync21(join28(projectPath, RUNTIME_AUTOLOAD.path))) {
47459
47712
  return runtimeVerdict(null, {
47460
47713
  addon: false,
47461
47714
  budgetMs,
@@ -47734,7 +47987,7 @@ class GodotServer {
47734
47987
  return 0;
47735
47988
  }
47736
47989
  try {
47737
- return reportAlreadyInTranscript(readFileSync23(game.transcript), readFileSync23(report));
47990
+ return reportAlreadyInTranscript(readFileSync24(game.transcript), readFileSync24(report));
47738
47991
  } catch (error) {
47739
47992
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
47740
47993
  return 0;
@@ -47977,7 +48230,8 @@ class GodotServer {
47977
48230
  startedAt,
47978
48231
  projectPath,
47979
48232
  ...this.servedBy(),
47980
- ...profile === undefined ? {} : { profile }
48233
+ ...profile === undefined ? {} : { profile },
48234
+ breakpoints: this.breakpointsForAGame(projectPath)
47981
48235
  },
47982
48236
  ...desktop === undefined ? {} : { desktop }
47983
48237
  });
@@ -48413,7 +48667,7 @@ class GodotServer {
48413
48667
  }
48414
48668
  const going = await this.runStillGoing(run);
48415
48669
  const projectPath = run.projectPath;
48416
- const projectGone = projectPath !== null && !existsSync20(join27(projectPath, "project.godot"));
48670
+ const projectGone = projectPath !== null && !existsSync21(join28(projectPath, "project.godot"));
48417
48671
  if (projectGone && callersName !== undefined) {
48418
48672
  return this.createErrorResponse(`callersOf reads the project's scripts, and the run's project at ${projectPath} has no project.godot any more.`, ["editor_run profile without callersOf answers with the profile itself"]);
48419
48673
  }
@@ -48701,7 +48955,7 @@ class GodotServer {
48701
48955
  notes.push(endedWithoutACode({
48702
48956
  throughEditor: run.throughEditor,
48703
48957
  announced: announced !== undefined || run.announcedPid !== undefined,
48704
- couldAnnounce: run.projectPath !== null && existsSync20(join27(run.projectPath, RUNTIME_AUTOLOAD.path)),
48958
+ couldAnnounce: run.projectPath !== null && existsSync21(join28(run.projectPath, RUNTIME_AUTOLOAD.path)),
48705
48959
  endedHere: typeof run.endedHere === "string",
48706
48960
  pickedUp: run.pickedUp === true
48707
48961
  }));
@@ -49266,7 +49520,7 @@ class GodotServer {
49266
49520
  if (!scriptPath.ok) {
49267
49521
  return this.createErrorResponse(scriptPath.reason);
49268
49522
  }
49269
- if (!existsSync20(scriptPath.absolutePath)) {
49523
+ if (!existsSync21(scriptPath.absolutePath)) {
49270
49524
  return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
49271
49525
  }
49272
49526
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -49594,7 +49848,7 @@ class GodotServer {
49594
49848
  async ownRunOnItsWay(projectPath, budgetMs) {
49595
49849
  const run = await this.currentRun();
49596
49850
  const project = projectPath ?? run?.projectPath ?? null;
49597
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync20(join27(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
49851
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync21(join28(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
49598
49852
  return null;
49599
49853
  }
49600
49854
  const already = runtimesAnnounced().running;
@@ -49679,12 +49933,25 @@ class GodotServer {
49679
49933
  }
49680
49934
  }
49681
49935
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
49682
- const screenshotPath = screenshotDir ? join27(screenshotDir, "capture.png") : null;
49936
+ const screenshotPath = screenshotDir ? join28(screenshotDir, "capture.png") : null;
49683
49937
  const staleNote = this.runtimeStaleNote(choice.endpoint);
49684
49938
  const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
49685
49939
  const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
49686
49940
 
49687
49941
  ${staleNote}`);
49942
+ const onGame = {
49943
+ pid: choice.endpoint.pid,
49944
+ what: `${asked.tool} ${asked.op}`,
49945
+ alongside: new Set
49946
+ };
49947
+ for (const other of this.callsOnGames) {
49948
+ if (other.pid === onGame.pid) {
49949
+ other.alongside.add(onGame.what);
49950
+ onGame.alongside.add(other.what);
49951
+ }
49952
+ }
49953
+ this.callsOnGames.add(onGame);
49954
+ const alongside = () => onGame.alongside.size === 0 ? {} : { ranAlongside: [...onGame.alongside], alongsideNote: ALONGSIDE_NOTE };
49688
49955
  try {
49689
49956
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
49690
49957
  if (!reply.ok && reply.requestId !== undefined) {
@@ -49695,7 +49962,8 @@ ${staleNote}`);
49695
49962
  waitedMs: timeoutMs,
49696
49963
  note: `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}`,
49697
49964
  ...answeredBy,
49698
- ...stale
49965
+ ...stale,
49966
+ ...alongside()
49699
49967
  });
49700
49968
  }
49701
49969
  if (!reply.ok) {
@@ -49703,7 +49971,7 @@ ${staleNote}`);
49703
49971
  }
49704
49972
  const { id: _id, ...payload } = reply.payload;
49705
49973
  if (!expectsScreenshot) {
49706
- return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale });
49974
+ return this.jsonTextResponse({ ...payload, ...answeredBy, ...stale, ...alongside() });
49707
49975
  }
49708
49976
  const returnedPath = readString(payload, "path");
49709
49977
  if (readString(payload, "type") !== "screenshot_file" || !returnedPath || !screenshotPath) {
@@ -49720,18 +49988,22 @@ ${staleNote}`);
49720
49988
  return this.createErrorResponse(`The capture was taken and could not be saved at ${saveTo}: ${errorMessage(error)}`);
49721
49989
  }
49722
49990
  }
49991
+ const ranWith = [...onGame.alongside];
49723
49992
  return {
49724
49993
  content: [
49725
49994
  {
49726
49995
  type: "text",
49727
- text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${staleNote === undefined ? "" : `
49996
+ text: `Screenshot captured: ${dimensions}${chosenAmongSeveral ? ` from pid ${own}, the game this server holds` : ""}${captureDetail(payload)}${saveTo === null ? "" : `. Saved to ${saveTo}`}${ranWith.length === 0 ? "" : `
49997
+
49998
+ Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}${staleNote === undefined ? "" : `
49728
49999
 
49729
50000
  ${staleNote}`}`
49730
50001
  },
49731
- { type: "image", data: readFileSync23(screenshotPath).toString("base64"), mimeType: "image/png" }
50002
+ { type: "image", data: readFileSync24(screenshotPath).toString("base64"), mimeType: "image/png" }
49732
50003
  ]
49733
50004
  };
49734
50005
  } finally {
50006
+ this.callsOnGames.delete(onGame);
49735
50007
  discard(screenshotDir);
49736
50008
  }
49737
50009
  }
@@ -49830,7 +50102,7 @@ async function runGodotServer() {
49830
50102
  const server = new GodotServer;
49831
50103
  await server.run();
49832
50104
  }
49833
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, EDITOR_FREE_INSTEAD, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
50105
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, run4, __dirname2, PARENT_ASKED_AFTER_MS = 5000, EDITOR_SERVERS_START_MS = 20000, HOLD_PING_MS = 2000, SLOWEST_FRAME_RATE = 20, ALONGSIDE_NOTE = "Another call on this game was running while this one was: calls sent together run together and are answered as each finishes, so this answer is not from after the other. Send a call meant to come after another once that one has answered.", OWN_DEBUGGER = " Its debugger is the keeper holding it rather than the editor, so the debug_* tools answer for it all the same: breakpoints stop it, and debug_control pause breaks into it wherever it is, which the editor offers no way to do.", PLAY_STARTS_WITHIN_MS = 30000, KEEPER_ANSWER_MS = 15000, KEEPER_DEBUG_MS, EXIT_REPORTED_WITHIN_MS = 2000, CONSOLE_WRAPPER, LONGEST_BOOT_NOTED_MS, PROFILE_SEND_MS = 5000, MOST_GROUPS = 20, RUNTIME_LISTENING_LINE = "[gdharness] runtime listening on ", VALUE_NAMES, BRIDGE_RETRY_MS = 2000, SUCCESSOR_CHECK_MS = 1e4, CONSOLE_WATCH_MS = 250, BOUNDARY_WAIT_MS = 2000, HANDOVER_MS, HANDOVER_POLL_MS = 250, CACHE_WRITE_MS = 2000, STREAMS_GRACE_MS = 2000, PLAYING_STATUS_MS = 1000, SCAN_START_MS = 2000, SCAN_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, EDITOR_FREE_INSTEAD, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, PROJECT_FILE_ARGUMENTS, DEBUG_STATE_CALLS, PROJECT_INFO_SECTIONS2, RUNNER_DIRECTORY = "addons/gdUnit4/", PROFILE_FUNCTIONS = 30, PROFILER_ON_MS = 3000, PROPERTY_PLACES = "It is a property, so each place says whether it writes it (access write), which runs its set block, or reads it (access read), which runs its get block: withinCalls on a read says nothing of the set block, nor on a write of the get block unless the write is a compound one such as +=. Inside the property's own blocks its name is the value stored, which the engine reads and writes without running them, so those lines are not listed.", CALLERS_FOUND_BY_NAME = "Found by name in the project's scripts, since the engine's profiler counts calls and time per function and records nothing about who made them: a method of the same name on another class is listed too, unless one class was asked about and it is called through another class by name or through a variable declared as one. Each place carries the profile's numbers for the function it is written in.", MOST_ENGINE_ENTRIES = 200, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
49834
50106
  var init_server = __esm(() => {
49835
50107
  init_dist();
49836
50108
  init_stdio();
@@ -49849,6 +50121,7 @@ var init_server = __esm(() => {
49849
50121
  init_editor_log();
49850
50122
  init_engine_run();
49851
50123
  init_errors();
50124
+ init_game_debugger();
49852
50125
  init_game_log();
49853
50126
  init_gdscript_source();
49854
50127
  init_godot_bridge();
@@ -49859,6 +50132,7 @@ var init_server = __esm(() => {
49859
50132
  init_junit();
49860
50133
  init_keeper_channel();
49861
50134
  init_launch();
50135
+ init_library_copies();
49862
50136
  init_lifecycle_log();
49863
50137
  init_lsp_client();
49864
50138
  init_outside();
@@ -49885,7 +50159,8 @@ var init_server = __esm(() => {
49885
50159
  init_tool_reference();
49886
50160
  init_update_check();
49887
50161
  run4 = promisify5(execFile5);
49888
- __dirname2 = dirname20(fileURLToPath5(import.meta.url));
50162
+ __dirname2 = dirname21(fileURLToPath5(import.meta.url));
50163
+ KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
49889
50164
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
49890
50165
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
49891
50166
  VALUE_NAMES = {
@@ -49943,8 +50218,8 @@ var init_server = __esm(() => {
49943
50218
  init_bridge_announce();
49944
50219
  init_errors();
49945
50220
  init_godot_path();
49946
- import { existsSync as existsSync21 } from "node:fs";
49947
- import { join as join28, resolve as resolve8 } from "node:path";
50221
+ import { existsSync as existsSync22 } from "node:fs";
50222
+ import { join as join29, resolve as resolve8 } from "node:path";
49948
50223
 
49949
50224
  // src/harnesses.ts
49950
50225
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
@@ -50909,10 +51184,10 @@ init_setup();
50909
51184
  init_setup();
50910
51185
  init_tool_definitions();
50911
51186
  init_tool_reference();
50912
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, readdirSync as readdirSync7, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
50913
- import { basename as basename4, dirname as dirname6, join as join12 } from "node:path";
51187
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
51188
+ import { basename as basename5, dirname as dirname7, join as join13 } from "node:path";
50914
51189
  var SKILL_NAME = "gdharness";
50915
- var SHARED_SKILLS = join12(".agents", "skills");
51190
+ var SHARED_SKILLS = join13(".agents", "skills");
50916
51191
  function skillMarkdown(version) {
50917
51192
  return `---
50918
51193
  name: ${SKILL_NAME}
@@ -51118,27 +51393,27 @@ comes back separately, under \`engine_messages_after_answer\`: none of it change
51118
51393
  function skillFiles(version) {
51119
51394
  return new Map([
51120
51395
  ["SKILL.md", skillMarkdown(version)],
51121
- [join12("references", "tools.md"), renderToolsMarkdown()]
51396
+ [join13("references", "tools.md"), renderToolsMarkdown()]
51122
51397
  ]);
51123
51398
  }
51124
51399
  function skillDirectories(harnesses, projectPath, exists) {
51125
- const directories = [join12(projectPath, SHARED_SKILLS, SKILL_NAME)];
51400
+ const directories = [join13(projectPath, SHARED_SKILLS, SKILL_NAME)];
51126
51401
  for (const harness of harnesses) {
51127
51402
  if (harness.skills === undefined) {
51128
51403
  continue;
51129
51404
  }
51130
- const own = join12(projectPath, harness.skills.dir);
51131
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join12(own, SKILL_NAME))) {
51132
- directories.push(join12(own, SKILL_NAME));
51405
+ const own = join13(projectPath, harness.skills.dir);
51406
+ if ((!harness.skills.shared || exists(own)) && !directories.includes(join13(own, SKILL_NAME))) {
51407
+ directories.push(join13(own, SKILL_NAME));
51133
51408
  }
51134
51409
  }
51135
51410
  return directories;
51136
51411
  }
51137
51412
  function everySkillDirectory(projectPath, harnesses) {
51138
- const directories = new Set([join12(projectPath, SHARED_SKILLS, SKILL_NAME)]);
51413
+ const directories = new Set([join13(projectPath, SHARED_SKILLS, SKILL_NAME)]);
51139
51414
  for (const harness of harnesses) {
51140
51415
  if (harness.skills !== undefined) {
51141
- directories.add(join12(projectPath, harness.skills.dir, SKILL_NAME));
51416
+ directories.add(join13(projectPath, harness.skills.dir, SKILL_NAME));
51142
51417
  }
51143
51418
  }
51144
51419
  return [...directories];
@@ -51146,33 +51421,33 @@ function everySkillDirectory(projectPath, harnesses) {
51146
51421
  function removeSkill(directories, projectPath) {
51147
51422
  const removed = [];
51148
51423
  for (const directory of directories) {
51149
- if (existsSync12(directory)) {
51424
+ if (existsSync13(directory)) {
51150
51425
  rmSync6(directory, { recursive: true, force: true });
51151
- pruneEmpty(dirname6(directory), projectPath);
51426
+ pruneEmpty(dirname7(directory), projectPath);
51152
51427
  removed.push(directory);
51153
51428
  }
51154
51429
  }
51155
51430
  return removed;
51156
51431
  }
51157
51432
  function pruneEmpty(directory, projectPath) {
51158
- const shared = join12(projectPath, ".agents");
51433
+ const shared = join13(projectPath, ".agents");
51159
51434
  let path = directory;
51160
- while (path !== projectPath && (basename4(path) === "skills" || path === shared)) {
51161
- if (readdirSync7(path).length > 0) {
51435
+ while (path !== projectPath && (basename5(path) === "skills" || path === shared)) {
51436
+ if (readdirSync8(path).length > 0) {
51162
51437
  return;
51163
51438
  }
51164
51439
  rmSync6(path, { recursive: true, force: true });
51165
- path = dirname6(path);
51440
+ path = dirname7(path);
51166
51441
  }
51167
51442
  }
51168
51443
  function writeSkill(directories, version) {
51169
51444
  const files = skillFiles(version);
51170
51445
  return directories.map((directory) => {
51171
- const replaced = existsSync12(directory);
51446
+ const replaced = existsSync13(directory);
51172
51447
  rmSync6(directory, { recursive: true, force: true });
51173
51448
  for (const [name, contents] of files) {
51174
- const path = join12(directory, name);
51175
- mkdirSync5(dirname6(path), { recursive: true });
51449
+ const path = join13(directory, name);
51450
+ mkdirSync5(dirname7(path), { recursive: true });
51176
51451
  writeFileSync7(path, contents, "utf8");
51177
51452
  }
51178
51453
  return { path: directory, replaced };
@@ -51180,18 +51455,18 @@ function writeSkill(directories, version) {
51180
51455
  }
51181
51456
 
51182
51457
  // src/version.ts
51183
- import { readFileSync as readFileSync11 } from "node:fs";
51184
- import { dirname as dirname7, join as join13 } from "node:path";
51458
+ import { readFileSync as readFileSync12 } from "node:fs";
51459
+ import { dirname as dirname8, join as join14 } from "node:path";
51185
51460
  import { fileURLToPath as fileURLToPath2 } from "node:url";
51186
51461
  function getLocalVersion() {
51187
- const currentDirectory = dirname7(fileURLToPath2(import.meta.url));
51462
+ const currentDirectory = dirname8(fileURLToPath2(import.meta.url));
51188
51463
  const packageCandidates = [
51189
- join13(currentDirectory, "..", "package.json"),
51190
- join13(currentDirectory, "..", "..", "package.json")
51464
+ join14(currentDirectory, "..", "package.json"),
51465
+ join14(currentDirectory, "..", "..", "package.json")
51191
51466
  ];
51192
51467
  for (const packagePath of packageCandidates) {
51193
51468
  try {
51194
- const value = JSON.parse(readFileSync11(packagePath, "utf-8"));
51469
+ const value = JSON.parse(readFileSync12(packagePath, "utf-8"));
51195
51470
  if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
51196
51471
  return value.version;
51197
51472
  }
@@ -51209,7 +51484,7 @@ class UsageError extends Refusal {
51209
51484
  function projectArgument(at) {
51210
51485
  const named = args.slice(at).find((value) => !value.startsWith("--"));
51211
51486
  const projectPath = resolve8(named ?? ".");
51212
- if (!existsSync21(join28(projectPath, "project.godot"))) {
51487
+ if (!existsSync22(join29(projectPath, "project.godot"))) {
51213
51488
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
51214
51489
  }
51215
51490
  return projectPath;
@@ -51254,7 +51529,7 @@ async function registerRuntime(godot, projectPath) {
51254
51529
  return;
51255
51530
  }
51256
51531
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
51257
- if (!existsSync21(join28(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
51532
+ if (!existsSync22(join29(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
51258
51533
  console.log(` that file is not in this project, so it boots with a missing script: restore it, or point the entry at res://${RUNTIME_AUTOLOAD.path}`);
51259
51534
  }
51260
51535
  }
@@ -51386,7 +51661,7 @@ async function setup() {
51386
51661
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
51387
51662
  }
51388
51663
  if (!args.includes("--no-skill")) {
51389
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync21);
51664
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync22);
51390
51665
  for (const written of writeSkill(directories, getLocalVersion())) {
51391
51666
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
51392
51667
  }
@@ -51500,7 +51775,7 @@ async function upgrade() {
51500
51775
  moved.push(took.from);
51501
51776
  }
51502
51777
  }
51503
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync21), version)) {
51778
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync22), version)) {
51504
51779
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
51505
51780
  }
51506
51781
  console.log(`