gdharness 1.2.37 → 1.2.39

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
@@ -8181,6 +8181,9 @@ class GameLog {
8181
8181
  count(severity) {
8182
8182
  return this.entries.filter((entry) => entry.severity === severity).length;
8183
8183
  }
8184
+ crashReport() {
8185
+ return this.entries.find((entry) => CRASH.test(entry.text));
8186
+ }
8184
8187
  everything() {
8185
8188
  return this.entries;
8186
8189
  }
@@ -8260,21 +8263,98 @@ function runEngine(executable, args, options = {}) {
8260
8263
  });
8261
8264
  });
8262
8265
  }
8266
+ function exitCodeText(code) {
8267
+ if (code === null) {
8268
+ return "exit code unknown";
8269
+ }
8270
+ if (code < 3221225472) {
8271
+ return `exit code ${code}`;
8272
+ }
8273
+ const hex = `0x${code.toString(16).toUpperCase()}`;
8274
+ return `exit code ${hex}, ${WINDOWS_STATUSES.get(code) ?? "a Windows error status"}`;
8275
+ }
8263
8276
  function howItEnded(run) {
8264
8277
  if (run.failure !== null) {
8265
8278
  return run.exitSignal === null ? run.failure : `ended by ${run.exitSignal}, because ${run.failure}`;
8266
8279
  }
8267
- return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}`;
8280
+ return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}`;
8268
8281
  }
8282
+ var WINDOWS_STATUSES;
8269
8283
  var init_engine_run = __esm(() => {
8270
8284
  init_call_signal();
8271
8285
  init_game_log();
8272
8286
  init_spawn();
8287
+ WINDOWS_STATUSES = new Map([
8288
+ [3221225477, "an access violation, so it crashed"],
8289
+ [3221225501, "an illegal instruction, so it crashed"],
8290
+ [3221225620, "an integer division by zero, so it crashed"],
8291
+ [3221225725, "a stack overflow, so it crashed"],
8292
+ [3221225781, "a library it needs was not found, so it never started"],
8293
+ [3221225786, "its console was closed or sent Ctrl+C"],
8294
+ [3221226356, "a corrupted heap, so it crashed"],
8295
+ [3221226505, "a stack buffer overrun or a fast fail, so it crashed"]
8296
+ ]);
8273
8297
  });
8274
8298
 
8275
- // src/library-copies.ts
8276
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
8299
+ // src/doc-cache.ts
8300
+ import { existsSync as existsSync5, readdirSync as readdirSync3, statSync as statSync4 } from "node:fs";
8301
+ import { homedir as homedir3 } from "node:os";
8277
8302
  import { basename as basename3, dirname as dirname4, isAbsolute as isAbsolute2, join as join5 } from "node:path";
8303
+ import process5 from "node:process";
8304
+ function editorCacheDirectory(godotPath, platform = process5.platform, env = process5.env, home = homedir3()) {
8305
+ const beside = dirname4(godotPath);
8306
+ const bundle = platform === "darwin" && basename3(beside) === "MacOS" && basename3(dirname4(beside)) === "Contents" ? dirname4(dirname4(dirname4(beside))) : null;
8307
+ for (const folder of bundle === null ? [beside] : [beside, bundle]) {
8308
+ if (existsSync5(join5(folder, "._sc_")) || existsSync5(join5(folder, "_sc_"))) {
8309
+ return join5(folder, "editor_data", "cache");
8310
+ }
8311
+ }
8312
+ if (platform === "win32") {
8313
+ const set = (name) => env[name] === "" ? undefined : env[name];
8314
+ return join5(set("LOCALAPPDATA") ?? set("TEMP") ?? join5(home, "AppData", "Local"), "Godot");
8315
+ }
8316
+ if (platform === "darwin") {
8317
+ return join5(home, "Library", "Caches", "Godot");
8318
+ }
8319
+ const xdg = env["XDG_CACHE_HOME"];
8320
+ return join5(xdg !== undefined && isAbsolute2(xdg) ? xdg : join5(home, ".cache"), "godot");
8321
+ }
8322
+ function docCachesWritten(directory) {
8323
+ const written = new Map;
8324
+ let names;
8325
+ try {
8326
+ names = readdirSync3(directory);
8327
+ } catch {
8328
+ return written;
8329
+ }
8330
+ for (const name of names.filter((one) => CACHE_FILE.test(one))) {
8331
+ try {
8332
+ written.set(join5(directory, name), statSync4(join5(directory, name)).mtimeMs);
8333
+ } catch {}
8334
+ }
8335
+ return written;
8336
+ }
8337
+ function docCacheCrash(ran, before, after, what) {
8338
+ const report = ran.log.crashReport();
8339
+ if (ran.exitCode === 0 && ran.exitSignal === null && report === undefined || ran.failure !== null) {
8340
+ return null;
8341
+ }
8342
+ const rewritten = [...after].find(([path, at]) => before.get(path) !== at)?.[0];
8343
+ if (rewritten === undefined) {
8344
+ return null;
8345
+ }
8346
+ const said = report === undefined ? "" : `, and its crash handler printed "${report.text}"`;
8347
+ return `The first ${what} ended badly (${howItEnded(ran)}${said}) after rebuilding the editor's class reference ` + `cache, ${rewritten}. Godot crashes as an import or an export exits when it finds that cache ` + "stale, which it is whenever another build of the same minor version wrote it last, such as 4.7.1 " + `beside 4.7.2, and the crashing run writes a fresh one. So the ${what} was run again, and this answer ` + "is from that second run.";
8348
+ }
8349
+ var CACHE_FILE;
8350
+ var init_doc_cache = __esm(() => {
8351
+ init_engine_run();
8352
+ CACHE_FILE = /^editor_doc_cache-\d+\.\d+\.res$/;
8353
+ });
8354
+
8355
+ // src/library-copies.ts
8356
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync3, renameSync, unlinkSync } from "node:fs";
8357
+ import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute3, join as join6 } from "node:path";
8278
8358
  function freeLibraryCopies(projectPath, platform = process.platform) {
8279
8359
  const setAside = [];
8280
8360
  const swept = [];
@@ -8282,26 +8362,26 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
8282
8362
  return { setAside, swept };
8283
8363
  }
8284
8364
  for (const library of windowsLibraries(projectPath)) {
8285
- const folder = dirname4(library);
8286
- const copy = `~${basename3(library)}`;
8287
- if (existsSync5(join5(folder, copy))) {
8288
- const aside = join5(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
8365
+ const folder = dirname5(library);
8366
+ const copy = `~${basename4(library)}`;
8367
+ if (existsSync6(join6(folder, copy))) {
8368
+ const aside = join6(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
8289
8369
  try {
8290
- renameSync(join5(folder, copy), aside);
8370
+ renameSync(join6(folder, copy), aside);
8291
8371
  setAside.push(aside);
8292
8372
  } catch {}
8293
8373
  }
8294
8374
  let names;
8295
8375
  try {
8296
- names = readdirSync3(folder);
8376
+ names = readdirSync4(folder);
8297
8377
  } catch {
8298
8378
  continue;
8299
8379
  }
8300
8380
  for (const name of names) {
8301
8381
  if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
8302
8382
  try {
8303
- unlinkSync(join5(folder, name));
8304
- swept.push(join5(folder, name));
8383
+ unlinkSync(join6(folder, name));
8384
+ swept.push(join6(folder, name));
8305
8385
  } catch {}
8306
8386
  }
8307
8387
  }
@@ -8309,7 +8389,7 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
8309
8389
  return { setAside, swept };
8310
8390
  }
8311
8391
  function windowsLibraries(projectPath) {
8312
- const listed = readOrNull(join5(projectPath, ".godot", "extension_list.cfg"));
8392
+ const listed = readOrNull(join6(projectPath, ".godot", "extension_list.cfg"));
8313
8393
  if (listed === null) {
8314
8394
  return [];
8315
8395
  }
@@ -8328,7 +8408,7 @@ function windowsLibraries(projectPath) {
8328
8408
  continue;
8329
8409
  }
8330
8410
  const named = inLibraries ? /^\s*windows[\w.]*\s*=\s*"([^"]+)"/.exec(row)?.[1] : undefined;
8331
- const library = named === undefined ? null : resolveResource(projectPath, dirname4(manifest), named);
8411
+ const library = named === undefined ? null : resolveResource(projectPath, dirname5(manifest), named);
8332
8412
  if (library !== null) {
8333
8413
  libraries.add(library);
8334
8414
  }
@@ -8341,9 +8421,9 @@ function resolveResource(projectPath, relativeTo, path) {
8341
8421
  return null;
8342
8422
  }
8343
8423
  if (path.startsWith("res://")) {
8344
- return join5(projectPath, path.slice("res://".length));
8424
+ return join6(projectPath, path.slice("res://".length));
8345
8425
  }
8346
- return isAbsolute2(path) ? path : join5(relativeTo, path);
8426
+ return isAbsolute3(path) ? path : join6(relativeTo, path);
8347
8427
  }
8348
8428
  function readOrNull(path) {
8349
8429
  try {
@@ -8356,33 +8436,33 @@ var init_library_copies = () => {};
8356
8436
 
8357
8437
  // src/reimport.ts
8358
8438
  import { createHash } from "node:crypto";
8359
- import { existsSync as existsSync6, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync4, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
8360
- import { basename as basename4, join as join6 } from "node:path";
8439
+ import { existsSync as existsSync7, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync5, utimesSync, writeFileSync as writeFileSync3 } from "node:fs";
8440
+ import { basename as basename5, join as join7 } from "node:path";
8361
8441
  function forceNextImport(projectPath, resourcePath) {
8362
8442
  const relative = resourcePath.replace(/^res:\/\//, "");
8363
- const sidecar = join6(projectPath, `${relative}.import`);
8364
- if (!existsSync6(sidecar)) {
8443
+ const sidecar = join7(projectPath, `${relative}.import`);
8444
+ if (!existsSync7(sidecar)) {
8365
8445
  return;
8366
8446
  }
8367
- const { atime, mtimeMs } = statSync4(sidecar);
8447
+ const { atime, mtimeMs } = statSync5(sidecar);
8368
8448
  writeFileSync3(sidecar, readFileSync4(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
8369
8449
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
8370
8450
  rmSync3(importRecord(projectPath, resourcePath), { force: true });
8371
8451
  }
8372
8452
  function importRecord(projectPath, resourcePath) {
8373
8453
  const digest = createHash("md5").update(resourcePath).digest("hex");
8374
- return join6(projectPath, ".godot", "imported", `${basename4(resourcePath)}-${digest}.md5`);
8454
+ return join7(projectPath, ".godot", "imported", `${basename5(resourcePath)}-${digest}.md5`);
8375
8455
  }
8376
8456
  var init_reimport = () => {};
8377
8457
 
8378
8458
  // src/alive.ts
8379
- import process5 from "node:process";
8459
+ import process6 from "node:process";
8380
8460
  function alive(pid) {
8381
8461
  if (pid === undefined || pid === null) {
8382
8462
  return false;
8383
8463
  }
8384
8464
  try {
8385
- process5.kill(pid, 0);
8465
+ process6.kill(pid, 0);
8386
8466
  return true;
8387
8467
  } catch (error) {
8388
8468
  return error.code === "EPERM";
@@ -8391,17 +8471,17 @@ function alive(pid) {
8391
8471
  var init_alive = () => {};
8392
8472
 
8393
8473
  // src/scratch.ts
8394
- import { existsSync as existsSync7, mkdtempSync, readdirSync as readdirSync4, rmSync as rmSync4 } from "node:fs";
8474
+ import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync5, rmSync as rmSync4 } from "node:fs";
8395
8475
  import { tmpdir as tmpdir2 } from "node:os";
8396
- import { join as join7 } from "node:path";
8397
- import process6 from "node:process";
8476
+ import { join as join8 } from "node:path";
8477
+ import process7 from "node:process";
8398
8478
  function scratchDirectory(kind) {
8399
- return mkdtempSync(join7(tmpdir2(), `gdharness-${kind}-${process6.pid}-`));
8479
+ return mkdtempSync(join8(tmpdir2(), `gdharness-${kind}-${process7.pid}-`));
8400
8480
  }
8401
8481
  function sweepAbandonedScratch(directory = tmpdir2()) {
8402
8482
  let entries;
8403
8483
  try {
8404
- entries = readdirSync4(directory);
8484
+ entries = readdirSync5(directory);
8405
8485
  } catch {
8406
8486
  return;
8407
8487
  }
@@ -8412,7 +8492,7 @@ function sweepAbandonedScratch(directory = tmpdir2()) {
8412
8492
  continue;
8413
8493
  }
8414
8494
  try {
8415
- remove(join7(directory, entry));
8495
+ remove(join8(directory, entry));
8416
8496
  } catch {}
8417
8497
  }
8418
8498
  }
@@ -8450,7 +8530,7 @@ function tryAgain(path, attempt) {
8450
8530
  lastTryOnExit();
8451
8531
  }
8452
8532
  const after = AGAIN_AFTER_MS[attempt];
8453
- if (after === undefined || !existsSync7(path)) {
8533
+ if (after === undefined || !existsSync8(path)) {
8454
8534
  waiting.delete(path);
8455
8535
  if (after === undefined) {
8456
8536
  atExit.add(path);
@@ -8471,7 +8551,7 @@ function lastTryOnExit() {
8471
8551
  return;
8472
8552
  }
8473
8553
  lastTryWatched = true;
8474
- process6.once("exit", () => {
8554
+ process7.once("exit", () => {
8475
8555
  for (const path of [...waiting, ...atExit]) {
8476
8556
  try {
8477
8557
  remove(path);
@@ -8498,8 +8578,8 @@ var init_scratch = __esm(() => {
8498
8578
  });
8499
8579
 
8500
8580
  // src/headless.ts
8501
- import { existsSync as existsSync8, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
8502
- import { dirname as dirname5, join as join8 } from "node:path";
8581
+ import { existsSync as existsSync9, readFileSync as readFileSync5, realpathSync as realpathSync2, writeFileSync as writeFileSync4 } from "node:fs";
8582
+ import { dirname as dirname6, join as join9 } from "node:path";
8503
8583
  function snakeCased(params) {
8504
8584
  const result = emptyRecord();
8505
8585
  for (const [key, value] of Object.entries(params)) {
@@ -8560,7 +8640,7 @@ function refusalsIn(log) {
8560
8640
  }
8561
8641
  function scriptFault(problems, script) {
8562
8642
  const spelled = (path) => `(${path.replaceAll("\\", "/").toLowerCase()}/`;
8563
- const directories = [...new Set([spelled(dirname5(script)), spelled(resolvedOr(dirname5(script)))])];
8643
+ const directories = [...new Set([spelled(dirname6(script)), spelled(resolvedOr(dirname6(script)))])];
8564
8644
  return problems.find((entry) => {
8565
8645
  const at = entry.detail.find((line) => line.startsWith("at: "))?.replaceAll("\\", "/").toLowerCase();
8566
8646
  return entry.severity === "error" && at !== undefined && directories.some((directory) => at.includes(directory));
@@ -8598,26 +8678,33 @@ async function runImport(godotPath, projectPath, options = {}) {
8598
8678
  };
8599
8679
  const first = await pass();
8600
8680
  const collided = librariesNotCopied(first.messages);
8601
- if (collided.length === 0) {
8681
+ const crash = first.docCacheNote;
8682
+ if (collided.length === 0 && crash === undefined) {
8602
8683
  return first;
8603
8684
  }
8604
- const second = await pass();
8685
+ const { docCacheNote: _, ...again } = await pass();
8686
+ const second = again.ok || crash === undefined ? again : { ...again, message: `${again.message.replace(/[.\s]+$/, "")}. ${crash}` };
8605
8687
  const still = librariesNotCopied(second.messages);
8606
8688
  return {
8607
8689
  ...second,
8608
- librariesRetried: collided,
8609
- ...still.length === 0 ? {} : { librariesNotLoaded: still },
8610
- extensionNote: extensionRetryNote(collided, still)
8690
+ ...collided.length === 0 ? {} : {
8691
+ librariesRetried: collided,
8692
+ ...still.length === 0 ? {} : { librariesNotLoaded: still },
8693
+ extensionNote: extensionRetryNote(collided, still)
8694
+ },
8695
+ ...crash === undefined ? {} : { docCacheNote: crash }
8611
8696
  };
8612
8697
  }
8613
8698
  async function importOnce(godotPath, projectPath) {
8614
8699
  const logDir = scratchDirectory("import");
8700
+ const caches = editorCacheDirectory(godotPath);
8701
+ const cachesBefore = docCachesWritten(caches);
8615
8702
  let ran;
8616
8703
  try {
8617
8704
  ran = await runEngine(godotPath, [
8618
8705
  "--headless",
8619
8706
  "--log-file",
8620
- join8(logDir, "engine.log"),
8707
+ join9(logDir, "engine.log"),
8621
8708
  "--path",
8622
8709
  projectPath,
8623
8710
  "--import"
@@ -8626,24 +8713,26 @@ async function importOnce(godotPath, projectPath) {
8626
8713
  discard(logDir);
8627
8714
  }
8628
8715
  const problems = problemsIn(ran.log);
8716
+ const crash = docCacheCrash(ran, cachesBefore, docCachesWritten(caches), "import");
8629
8717
  if (ran.exitCode === 0 && ran.failure === null) {
8630
- return { ok: true, ...reported2(problems) };
8718
+ return { ok: true, ...reported2(problems), ...crash === null ? {} : { docCacheNote: crash } };
8631
8719
  }
8632
8720
  return {
8633
8721
  ok: false,
8634
8722
  message: `the import pass failed (${howItEnded(ran)}): ${reason(ran.log, problems)}`,
8635
- ...reported2(problems)
8723
+ ...reported2(problems),
8724
+ ...crash === null ? {} : { docCacheNote: crash }
8636
8725
  };
8637
8726
  }
8638
8727
  async function runOperation(engine, operation, params, projectPath) {
8639
8728
  const paramsDir = scratchDirectory("params");
8640
- const paramsFile = join8(paramsDir, `${operation}.json`);
8641
- const answerFile = join8(paramsDir, "answer.json");
8729
+ const paramsFile = join9(paramsDir, `${operation}.json`);
8730
+ const answerFile = join9(paramsDir, "answer.json");
8642
8731
  writeFileSync4(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
8643
8732
  const args = [
8644
8733
  "--headless",
8645
8734
  "--log-file",
8646
- join8(paramsDir, "engine.log"),
8735
+ join9(paramsDir, "engine.log"),
8647
8736
  "--path",
8648
8737
  projectPath,
8649
8738
  "--script",
@@ -8731,7 +8820,7 @@ async function runOperation(engine, operation, params, projectPath) {
8731
8820
  };
8732
8821
  }
8733
8822
  function answerIn(file) {
8734
- if (!existsSync8(file)) {
8823
+ if (!existsSync9(file)) {
8735
8824
  return { kind: "none" };
8736
8825
  }
8737
8826
  const text = readFileSync5(file, "utf8");
@@ -8747,6 +8836,7 @@ function answerIn(file) {
8747
8836
  }
8748
8837
  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.", BEFORE_OPERATION_NOTE = "Printed while the engine started the project's autoloads, before this operation ran: an operation that compiles the project's scripts waits for them, because the engine knows an autoload's name only once it is in the tree. An error raised in a project script here is that autoload's own.", OWN_ERROR = "[ERROR] ", ANSWERED = "[gdharness] answered", WAITING = "[gdharness] waiting for the autoloads", BEGUN = "[gdharness] begun", MESSAGES_KEPT = 200, ERRORS_NAMED = 3;
8749
8838
  var init_headless = __esm(() => {
8839
+ init_doc_cache();
8750
8840
  init_engine_run();
8751
8841
  init_library_copies();
8752
8842
  init_reimport();
@@ -8754,9 +8844,9 @@ var init_headless = __esm(() => {
8754
8844
  });
8755
8845
 
8756
8846
  // src/update-check.ts
8757
- import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8758
- import { homedir as homedir3, tmpdir as tmpdir3 } from "node:os";
8759
- import { join as join9 } from "node:path";
8847
+ import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
8848
+ import { homedir as homedir4, tmpdir as tmpdir3 } from "node:os";
8849
+ import { join as join10 } from "node:path";
8760
8850
  function packageUrl(root) {
8761
8851
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
8762
8852
  }
@@ -8776,27 +8866,27 @@ function cacheDirectory(environment) {
8776
8866
  const value = environment[name];
8777
8867
  return value !== undefined && value !== "" ? value : null;
8778
8868
  };
8779
- const home = set("HOME") ?? homedir3();
8869
+ const home = set("HOME") ?? homedir4();
8780
8870
  if (process.platform === "win32") {
8781
- return join9(set("LOCALAPPDATA") ?? home, "gdharness");
8871
+ return join10(set("LOCALAPPDATA") ?? home, "gdharness");
8782
8872
  }
8783
8873
  if (process.platform === "darwin") {
8784
- return join9(home, "Library", "Caches", "gdharness");
8874
+ return join10(home, "Library", "Caches", "gdharness");
8785
8875
  }
8786
- return join9(set("XDG_CACHE_HOME") ?? join9(home, ".cache"), "gdharness");
8876
+ return join10(set("XDG_CACHE_HOME") ?? join10(home, ".cache"), "gdharness");
8787
8877
  }
8788
8878
  function cacheFile(environment = process.env) {
8789
8879
  try {
8790
8880
  const directory = cacheDirectory(environment);
8791
8881
  mkdirSync3(directory, { recursive: true, mode: 448 });
8792
- return join9(directory, "update-check.json");
8882
+ return join10(directory, "update-check.json");
8793
8883
  } catch {
8794
- return join9(tmpdir3(), "gdharness-update-check.json");
8884
+ return join10(tmpdir3(), "gdharness-update-check.json");
8795
8885
  }
8796
8886
  }
8797
8887
  function readCache(path) {
8798
8888
  try {
8799
- if (!existsSync9(path)) {
8889
+ if (!existsSync10(path)) {
8800
8890
  return null;
8801
8891
  }
8802
8892
  const parsed = JSON.parse(readFileSync6(path, "utf8"));
@@ -9031,7 +9121,7 @@ var init_server_version = __esm(() => {
9031
9121
 
9032
9122
  // src/issues.ts
9033
9123
  import { createHash as createHash2 } from "node:crypto";
9034
- import process7 from "node:process";
9124
+ import process8 from "node:process";
9035
9125
  function feedbackNotice(every) {
9036
9126
  return JSON.stringify({
9037
9127
  gdharness_feedback: {
@@ -9042,9 +9132,9 @@ function feedbackNotice(every) {
9042
9132
  }, null, 2);
9043
9133
  }
9044
9134
  function runtime() {
9045
- const versions = process7.versions;
9135
+ const versions = process8.versions;
9046
9136
  const bun = versions["bun"];
9047
- return bun === undefined ? `node ${process7.versions.node}` : `bun ${bun}`;
9137
+ return bun === undefined ? `node ${process8.versions.node}` : `bun ${bun}`;
9048
9138
  }
9049
9139
  function defectSignature(where, message) {
9050
9140
  const normalised = `${where}
@@ -9056,7 +9146,7 @@ function facts(where, message, godotVersion) {
9056
9146
  ["Where", where],
9057
9147
  ["Error", message],
9058
9148
  ["Version", `gdharness ${SERVER_VERSION}`],
9059
- ["Runtime", `${runtime()}, ${process7.platform} ${process7.arch}`],
9149
+ ["Runtime", `${runtime()}, ${process8.platform} ${process8.arch}`],
9060
9150
  ["Godot", godotVersion ?? "not known at the point this failed"],
9061
9151
  ["Signature", defectSignature(where, message)]
9062
9152
  ];
@@ -9066,7 +9156,7 @@ function filledTemplate(where, message, godotVersion) {
9066
9156
  `**gdharness version**: ${SERVER_VERSION}`,
9067
9157
  `**Godot version**: ${godotVersion ?? ""}`,
9068
9158
  `**Bun version**: ${runtime()}`,
9069
- `**OS**: ${process7.platform} ${process7.arch}`,
9159
+ `**OS**: ${process8.platform} ${process8.arch}`,
9070
9160
  "**MCP client**:",
9071
9161
  "",
9072
9162
  "## What you did",
@@ -9806,8 +9896,8 @@ var init_gdscript_source = __esm(() => {
9806
9896
  });
9807
9897
 
9808
9898
  // src/class-cache.ts
9809
- import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync8, statSync as statSync5 } from "node:fs";
9810
- import { join as join10, posix } from "node:path";
9899
+ import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync6 } from "node:fs";
9900
+ import { join as join11, posix } from "node:path";
9811
9901
  function isToolScript(source) {
9812
9902
  return TOOL.test(codeOf(source));
9813
9903
  }
@@ -9823,14 +9913,14 @@ function declaredClasses(projectPath) {
9823
9913
  }
9824
9914
  function eachScript(projectPath, each) {
9825
9915
  const visit = (directory, prefix) => {
9826
- if (existsSync10(join10(directory, ".gdignore"))) {
9916
+ if (existsSync11(join11(directory, ".gdignore"))) {
9827
9917
  return;
9828
9918
  }
9829
- for (const entry of readdirSync5(directory, { withFileTypes: true })) {
9919
+ for (const entry of readdirSync6(directory, { withFileTypes: true })) {
9830
9920
  if (entry.name.startsWith(".")) {
9831
9921
  continue;
9832
9922
  }
9833
- const path = join10(directory, entry.name);
9923
+ const path = join11(directory, entry.name);
9834
9924
  if (entry.isDirectory()) {
9835
9925
  visit(path, `${prefix}${entry.name}/`);
9836
9926
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
@@ -9856,8 +9946,8 @@ function scriptsNaming(projectPath, classes) {
9856
9946
  return naming.sort((a, b) => a.script.localeCompare(b.script));
9857
9947
  }
9858
9948
  function cachedClasses(projectPath) {
9859
- const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
9860
- if (!existsSync10(cache)) {
9949
+ const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
9950
+ if (!existsSync11(cache)) {
9861
9951
  return null;
9862
9952
  }
9863
9953
  const listed = new Map;
@@ -10127,9 +10217,9 @@ function uncachedClassNote(types) {
10127
10217
  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.";
10128
10218
  }
10129
10219
  function cacheWrittenAt(projectPath) {
10130
- const cache = join10(projectPath, ".godot", "global_script_class_cache.cfg");
10220
+ const cache = join11(projectPath, ".godot", "global_script_class_cache.cfg");
10131
10221
  try {
10132
- return statSync5(cache).mtimeMs;
10222
+ return statSync6(cache).mtimeMs;
10133
10223
  } catch {
10134
10224
  return null;
10135
10225
  }
@@ -10141,14 +10231,14 @@ function declaredSince(projectPath, paths, names, since) {
10141
10231
  return false;
10142
10232
  }
10143
10233
  try {
10144
- return statSync5(join10(projectPath, path.slice("res://".length))).mtimeMs > since;
10234
+ return statSync6(join11(projectPath, path.slice("res://".length))).mtimeMs > since;
10145
10235
  } catch {
10146
10236
  return false;
10147
10237
  }
10148
10238
  });
10149
10239
  }
10150
10240
  function cachedAtMissingPaths(cached, projectPath) {
10151
- return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync10(join10(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
10241
+ return [...cached].filter(([, path]) => path.startsWith("res://") && !existsSync11(join11(projectPath, path.slice("res://".length)))).map(([name]) => name).sort();
10152
10242
  }
10153
10243
  function staleAgainst(cached, projectPath) {
10154
10244
  return [...declaredClasses(projectPath)].filter(([name, path]) => cached.get(name) !== path).map(([name]) => name);
@@ -10173,8 +10263,8 @@ var init_class_cache = __esm(() => {
10173
10263
  });
10174
10264
 
10175
10265
  // src/project-scan.ts
10176
- import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "node:fs";
10177
- import { extname, join as join11 } from "node:path";
10266
+ import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync9 } from "node:fs";
10267
+ import { extname, join as join12 } from "node:path";
10178
10268
  function skipped(name) {
10179
10269
  return name.startsWith(".") || SKIPPED.has(name);
10180
10270
  }
@@ -10182,10 +10272,10 @@ function steppedOver(directory) {
10182
10272
  return gdignored(directory) || installedByUs(directory);
10183
10273
  }
10184
10274
  function gdignored(directory) {
10185
- return existsSync11(join11(directory, ".gdignore"));
10275
+ return existsSync12(join12(directory, ".gdignore"));
10186
10276
  }
10187
10277
  function installedByUs(directory) {
10188
- return existsSync11(join11(directory, VERSION_MARKER));
10278
+ return existsSync12(join12(directory, VERSION_MARKER));
10189
10279
  }
10190
10280
  function projectStructure(projectPath) {
10191
10281
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -10193,12 +10283,12 @@ function projectStructure(projectPath) {
10193
10283
  if (steppedOver(directory)) {
10194
10284
  return;
10195
10285
  }
10196
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10286
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10197
10287
  if (skipped(entry.name)) {
10198
10288
  continue;
10199
10289
  }
10200
10290
  if (entry.isDirectory()) {
10201
- visit(join11(directory, entry.name));
10291
+ visit(join12(directory, entry.name));
10202
10292
  } else if (entry.isFile()) {
10203
10293
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10204
10294
  if (SCENE_EXTENSIONS.has(extension)) {
@@ -10222,13 +10312,13 @@ function projectFiles(projectPath, extensions) {
10222
10312
  if (steppedOver(directory)) {
10223
10313
  return;
10224
10314
  }
10225
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10315
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10226
10316
  if (skipped(entry.name)) {
10227
10317
  continue;
10228
10318
  }
10229
10319
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10230
10320
  if (entry.isDirectory()) {
10231
- visit(join11(directory, entry.name), spelled);
10321
+ visit(join12(directory, entry.name), spelled);
10232
10322
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
10233
10323
  found.push(spelled);
10234
10324
  }
@@ -10243,20 +10333,20 @@ function scriptsWithoutUid(projectPath) {
10243
10333
  if (steppedOver(directory)) {
10244
10334
  return;
10245
10335
  }
10246
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10336
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10247
10337
  if (skipped(entry.name)) {
10248
10338
  continue;
10249
10339
  }
10250
10340
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
10251
10341
  if (entry.isDirectory()) {
10252
- visit(join11(directory, entry.name), spelled);
10342
+ visit(join12(directory, entry.name), spelled);
10253
10343
  continue;
10254
10344
  }
10255
10345
  if (!entry.isFile()) {
10256
10346
  continue;
10257
10347
  }
10258
10348
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
10259
- if (CARRIES_A_UID.has(extension) && !existsSync11(join11(directory, `${entry.name}.uid`))) {
10349
+ if (CARRIES_A_UID.has(extension) && !existsSync12(join12(directory, `${entry.name}.uid`))) {
10260
10350
  found.push(spelled);
10261
10351
  }
10262
10352
  }
@@ -10273,11 +10363,11 @@ function projectTexts(projectPath) {
10273
10363
  if (steppedOver(directory)) {
10274
10364
  return;
10275
10365
  }
10276
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10366
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10277
10367
  if (skipped(entry.name)) {
10278
10368
  continue;
10279
10369
  }
10280
- const absolute = join11(directory, entry.name);
10370
+ const absolute = join12(directory, entry.name);
10281
10371
  const spelled = `${prefix}${entry.name}`;
10282
10372
  if (entry.isDirectory()) {
10283
10373
  visit(absolute, `${spelled}/`);
@@ -10303,11 +10393,11 @@ function ignoredTexts(projectPath) {
10303
10393
  return;
10304
10394
  }
10305
10395
  const under = ignored || gdignored(directory);
10306
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10396
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10307
10397
  if (skipped(entry.name)) {
10308
10398
  continue;
10309
10399
  }
10310
- const absolute = join11(directory, entry.name);
10400
+ const absolute = join12(directory, entry.name);
10311
10401
  const spelled = `${prefix}${entry.name}`;
10312
10402
  if (entry.isDirectory()) {
10313
10403
  visit(absolute, `${spelled}/`, under);
@@ -10340,14 +10430,14 @@ function searchProject(projectPath, options) {
10340
10430
  if (steppedOver(directory)) {
10341
10431
  return;
10342
10432
  }
10343
- for (const entry of readdirSync6(directory, { withFileTypes: true })) {
10433
+ for (const entry of readdirSync7(directory, { withFileTypes: true })) {
10344
10434
  if (full()) {
10345
10435
  return;
10346
10436
  }
10347
10437
  if (skipped(entry.name)) {
10348
10438
  continue;
10349
10439
  }
10350
- const entryPath = join11(directory, entry.name);
10440
+ const entryPath = join12(directory, entry.name);
10351
10441
  if (entry.isDirectory()) {
10352
10442
  visit(entryPath);
10353
10443
  continue;
@@ -10792,16 +10882,16 @@ function readParams(params, key) {
10792
10882
  import { createHash as createHash3 } from "node:crypto";
10793
10883
  import {
10794
10884
  cpSync,
10795
- existsSync as existsSync12,
10885
+ existsSync as existsSync13,
10796
10886
  mkdirSync as mkdirSync4,
10797
- readdirSync as readdirSync7,
10887
+ readdirSync as readdirSync8,
10798
10888
  readFileSync as readFileSync11,
10799
10889
  rmdirSync,
10800
10890
  rmSync as rmSync5,
10801
- statSync as statSync6,
10891
+ statSync as statSync7,
10802
10892
  writeFileSync as writeFileSync6
10803
10893
  } from "node:fs";
10804
- import { dirname as dirname6, join as join12 } from "node:path";
10894
+ import { dirname as dirname7, join as join13 } from "node:path";
10805
10895
  import { fileURLToPath } from "node:url";
10806
10896
  function editorAddonDigest(from = shippedAddonsDirectory()) {
10807
10897
  return addonsDigest(EDITOR_PLUGINS, from);
@@ -10812,13 +10902,13 @@ function runtimeAddonDigest(from = shippedAddonsDirectory()) {
10812
10902
  function addonsDigest(names, from) {
10813
10903
  const hash = createHash3("sha256");
10814
10904
  const walk = (directory, relative) => {
10815
- for (const name of readdirSync7(directory).sort()) {
10905
+ for (const name of readdirSync8(directory).sort()) {
10816
10906
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
10817
10907
  continue;
10818
10908
  }
10819
- const path = join12(directory, name);
10909
+ const path = join13(directory, name);
10820
10910
  const inside = `${relative}/${name}`;
10821
- if (statSync6(path).isDirectory()) {
10911
+ if (statSync7(path).isDirectory()) {
10822
10912
  walk(path, inside);
10823
10913
  } else {
10824
10914
  hash.update(`${inside}
@@ -10830,7 +10920,7 @@ function addonsDigest(names, from) {
10830
10920
  }
10831
10921
  };
10832
10922
  for (const name of names) {
10833
- walk(join12(from, name), name);
10923
+ walk(join13(from, name), name);
10834
10924
  }
10835
10925
  return hash.digest("hex");
10836
10926
  }
@@ -10861,44 +10951,44 @@ function installedRuntimeDigest(projectPath) {
10861
10951
  return digestMarker(projectPath, RUNTIME_ADDON);
10862
10952
  }
10863
10953
  function digestMarker(projectPath, addon) {
10864
- const marker = join12(projectPath, "addons", addon, DIGEST_MARKER);
10865
- if (!existsSync12(marker)) {
10954
+ const marker = join13(projectPath, "addons", addon, DIGEST_MARKER);
10955
+ if (!existsSync13(marker)) {
10866
10956
  return null;
10867
10957
  }
10868
10958
  const said = readFileSync11(marker, "utf8").trim();
10869
10959
  return said === "" ? null : said;
10870
10960
  }
10871
10961
  function shippedAddonsDirectory() {
10872
- return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "addons");
10962
+ return join13(dirname7(fileURLToPath(import.meta.url)), "godot", "addons");
10873
10963
  }
10874
10964
  function shippedOperationsScript() {
10875
- return join12(dirname6(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
10965
+ return join13(dirname7(fileURLToPath(import.meta.url)), "godot", "operations", "godot_operations.gd");
10876
10966
  }
10877
10967
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
10878
10968
  const installed = [];
10879
10969
  for (const name of ADDONS) {
10880
- const source = join12(from, name);
10881
- if (!existsSync12(join12(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
10970
+ const source = join13(from, name);
10971
+ if (!existsSync13(join13(source, name === RUNTIME_ADDON ? "runtime_autoload.gd" : "plugin.cfg"))) {
10882
10972
  throw new Error(`The package holds no ${name} addon at ${source}.`);
10883
10973
  }
10884
- const target = join12(projectPath, "addons", name);
10885
- const replaced = existsSync12(target);
10974
+ const target = join13(projectPath, "addons", name);
10975
+ const replaced = existsSync13(target);
10886
10976
  rmSync5(target, { recursive: true, force: true });
10887
- mkdirSync4(dirname6(target), { recursive: true });
10977
+ mkdirSync4(dirname7(target), { recursive: true });
10888
10978
  cpSync(source, target, { recursive: true });
10889
- writeFileSync6(join12(target, VERSION_MARKER), `${SERVER_VERSION}
10979
+ writeFileSync6(join13(target, VERSION_MARKER), `${SERVER_VERSION}
10890
10980
  `);
10891
10981
  installed.push({ name, path: target, replaced });
10892
10982
  }
10893
- writeFileSync6(join12(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10983
+ writeFileSync6(join13(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
10894
10984
  `);
10895
- writeFileSync6(join12(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10985
+ writeFileSync6(join13(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
10896
10986
  `);
10897
10987
  return installed;
10898
10988
  }
10899
10989
  function installedAddonVersion(projectPath) {
10900
- const marker = join12(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10901
- if (!existsSync12(marker)) {
10990
+ const marker = join13(projectPath, "addons", ADDONS[0], VERSION_MARKER);
10991
+ if (!existsSync13(marker)) {
10902
10992
  return null;
10903
10993
  }
10904
10994
  const said = readFileSync11(marker, "utf8").trim();
@@ -10907,14 +10997,14 @@ function installedAddonVersion(projectPath) {
10907
10997
  function removeAddons(projectPath) {
10908
10998
  const removed = [];
10909
10999
  for (const name of ADDONS) {
10910
- const target = join12(projectPath, "addons", name);
10911
- if (existsSync12(target)) {
11000
+ const target = join13(projectPath, "addons", name);
11001
+ if (existsSync13(target)) {
10912
11002
  rmSync5(target, { recursive: true, force: true });
10913
11003
  removed.push(target);
10914
11004
  }
10915
11005
  }
10916
- const addons = join12(projectPath, "addons");
10917
- if (removed.length > 0 && existsSync12(addons) && readdirSync7(addons).length === 0) {
11006
+ const addons = join13(projectPath, "addons");
11007
+ if (removed.length > 0 && existsSync13(addons) && readdirSync8(addons).length === 0) {
10918
11008
  rmdirSync(addons);
10919
11009
  removed.push(addons);
10920
11010
  }
@@ -10948,7 +11038,7 @@ function loaderAmong(projectPath, named) {
10948
11038
  }
10949
11039
  let source = null;
10950
11040
  try {
10951
- source = readFileSync11(join12(projectPath, path), "utf8");
11041
+ source = readFileSync11(join13(projectPath, path), "utf8");
10952
11042
  } catch {}
10953
11043
  if (source?.includes(RUNTIME_AUTOLOAD.path)) {
10954
11044
  return { name: entry, path, exists: true };
@@ -10990,10 +11080,10 @@ function enabledPlugins(settings) {
10990
11080
  function inspectProject(projectPath) {
10991
11081
  const problems = [];
10992
11082
  const addons = ADDONS.map((name) => {
10993
- const directory = join12(projectPath, "addons", name);
10994
- const marker = join12(directory, VERSION_MARKER);
10995
- const installed = existsSync12(directory);
10996
- const version = existsSync12(marker) ? readFileSync11(marker, "utf8").trim() : null;
11083
+ const directory = join13(projectPath, "addons", name);
11084
+ const marker = join13(directory, VERSION_MARKER);
11085
+ const installed = existsSync13(directory);
11086
+ const version = existsSync13(marker) ? readFileSync11(marker, "utf8").trim() : null;
10997
11087
  const current = version === SERVER_VERSION;
10998
11088
  if (!installed) {
10999
11089
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -11002,7 +11092,7 @@ function inspectProject(projectPath) {
11002
11092
  }
11003
11093
  return { name, installed, version, current };
11004
11094
  });
11005
- const settings = parseProjectGodot(readFileSync11(join12(projectPath, "project.godot"), "utf8"));
11095
+ const settings = parseProjectGodot(readFileSync11(join13(projectPath, "project.godot"), "utf8"));
11006
11096
  const pluginsEnabled = enabledPlugins(settings);
11007
11097
  for (const name of EDITOR_PLUGINS) {
11008
11098
  if (!pluginsEnabled.includes(name)) {
@@ -11018,7 +11108,7 @@ function inspectProject(projectPath) {
11018
11108
  const tracked = trackedByGit(projectPath, [...named.values()]);
11019
11109
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
11020
11110
  for (const [name, path] of named) {
11021
- if (!existsSync12(join12(projectPath, path))) {
11111
+ if (!existsSync13(join13(projectPath, path))) {
11022
11112
  if (!addonMissing(path)) {
11023
11113
  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`);
11024
11114
  }
@@ -11334,7 +11424,7 @@ var init_tool_definitions = __esm(() => {
11334
11424
  name: "project_info",
11335
11425
  title: "Project info",
11336
11426
  effect: "read",
11337
- description: "What a project is: its name and main scene from project.godot, the Godot that answers, and how many scenes, scripts and assets it holds, with optional sections on top.",
11427
+ description: "What a project is: its name and main scene from project.godot, the Godot that answers, and how many scenes, scripts and assets it holds, other counting every remaining file, leaving out the addons gdharness installed and any folder holding a .gdignore, with optional sections on top.",
11338
11428
  parameters: {
11339
11429
  projectPath: PROJECT_PATH,
11340
11430
  include: {
@@ -11576,7 +11666,7 @@ var init_tool_definitions = __esm(() => {
11576
11666
  },
11577
11667
  reimport: {
11578
11668
  effect: "destructive",
11579
- summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. The editor scans first, since its reimport passes over a file it has not taken in, such as one an installer has just written; what that scan imported is named under importedByTheScan and is not imported again unless force asks. Answered from a second status read: reimported lists what is current now, notReimported what is not, one entry per status and reason with the paths sharing them, and via says which of the two did the work. A whole-project reimport also answers missingSource, the .import files whose source is not beside them, empty when there are none, whether or not anything needed reimporting; one resourcePath is not a walk of the project and leaves it out. stillImporting is an editor that had not finished within timeoutMs",
11669
+ summary: "reimport what status says is outdated or failed, one resource with resourcePath, or what is current as well with force. Through the open editor when one serves the project, so it reloads what it holds, waiting for it to finish; otherwise through the engine's own import pass. The editor scans first, since its reimport passes over a file it has not taken in, such as one an installer has just written; what that scan imported is named under importedByTheScan and is not imported again unless force asks. Answered from a second status read: reimported lists what is current now, notReimported what is not, one entry per status and reason with the paths sharing them, and via says which of the two did the work. When nothing needs reimporting, nothing runs, and the answer has the two lists empty, a note saying so, and no via. A whole-project reimport also answers missingSource, the .import files whose source is not beside them, empty when there are none, whether or not anything needed reimporting; one resourcePath is not a walk of the project and leaves it out. stillImporting is an editor that had not finished within timeoutMs",
11580
11670
  requires: []
11581
11671
  },
11582
11672
  uid: {
@@ -11660,7 +11750,7 @@ var init_tool_definitions = __esm(() => {
11660
11750
  timeoutMs: {
11661
11751
  type: "integer",
11662
11752
  minimum: 1,
11663
- description: "How long the run may take before it is killed. Default 600000. A run killed at the limit answers timedOut and silentForMs, how long it had printed nothing; it is called hung only when that was half the limit, or thirty seconds of a longer one, and otherwise it was still running and wants a longer timeoutMs or a narrower path."
11753
+ description: "How long the run may take before it is killed. Default 600000. A run killed at the limit answers timedOut and silentForMs, how long it had printed nothing; it is called hung only when that was half the limit, or thirty seconds of a longer one, and otherwise it was still running and wants a longer timeoutMs or a narrower path, unless it ended repeating one script error, which the verdict names instead. A run whose autoload did not compile is stopped as soon as the engine says so, and names the script that did not under scriptErrors."
11664
11754
  },
11665
11755
  printed: {
11666
11756
  type: "string",
@@ -11679,7 +11769,7 @@ var init_tool_definitions = __esm(() => {
11679
11769
  rootNodeType: {
11680
11770
  type: "string",
11681
11771
  ops: ["create"],
11682
- description: "create: the root node class. Default Node."
11772
+ description: "create: the root node class, an engine class or a class_name a project script declares, which the root is made as with that script attached. Default Node."
11683
11773
  },
11684
11774
  rootNodeName: {
11685
11775
  type: "string",
@@ -11717,7 +11807,7 @@ var init_tool_definitions = __esm(() => {
11717
11807
  {
11718
11808
  name: "scene_node",
11719
11809
  title: "Scene nodes",
11720
- description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene, and for a node that belongs to a scene this one instances or inherits. ${WITHOUT_AN_EDITOR}`,
11810
+ description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene, and for a node that belongs to a scene this one instances or inherits. ${WITHOUT_AN_EDITOR}`,
11721
11811
  parameters: {
11722
11812
  projectPath: PROJECT_PATH,
11723
11813
  scenePath: SCENE_PATH,
@@ -11730,7 +11820,7 @@ var init_tool_definitions = __esm(() => {
11730
11820
  nodeType: {
11731
11821
  type: "string",
11732
11822
  ops: ["add"],
11733
- description: 'add: the node class, such as "CharacterBody2D". Give this or instancePath.'
11823
+ description: 'add: the node class, such as "CharacterBody2D", or a class_name a project script declares, which the node is made as with that script attached. Give this or instancePath.'
11734
11824
  },
11735
11825
  instancePath: {
11736
11826
  type: "string",
@@ -12075,7 +12165,7 @@ var init_tool_definitions = __esm(() => {
12075
12165
  resourceType: {
12076
12166
  type: "string",
12077
12167
  ops: ["create"],
12078
- description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D".'
12168
+ description: 'create: the resource class, such as "PhysicsMaterial" or "StandardMaterial3D", or a class_name a project script declares, which the resource is made as with that script attached.'
12079
12169
  },
12080
12170
  properties: { ...PROPERTIES, ops: ["create", "modify"] },
12081
12171
  script: {
@@ -12269,6 +12359,17 @@ var init_tool_definitions = __esm(() => {
12269
12359
  ops: ["profile"],
12270
12360
  description: "profile: also answer, under callers, where the project's scripts name this function in code: its name, or Class.name for the functions of one class. Each place has its script, line, the named function it is written in (within), whether the function is called there or passed on as a Callable (called), and the profile's calls, self and total milliseconds for the function it is in, those taking the most time first, forty at most. Found by name in the source, since the engine's profiler records nothing about who made a call, so a method of the same name on another class is listed too, except, when Class.name was asked, one called through another class of the project by name or through a parameter, local or class variable declared as one, and a call made through a string is not. A parameter, local or class variable of the same name is not the function and is not listed. A property's get or set block, which the profile names Class.@name_getter or Class.@name_setter, is asked about by that name or by the property's: the places are where the property is named, each saying instead of called whether it writes the property (access write), which runs the set block, or reads it (access read), which runs the get block, and the rows are both blocks'. 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 places."
12271
12361
  },
12362
+ mark: {
12363
+ type: "boolean",
12364
+ ops: ["profile"],
12365
+ description: `profile: answer as usual and set the run's mark at these totals, so a later profile with since: "mark" answers for one action, a press or a commit, apart from the boot and everything before it. The mark is kept beside the run's profile, so a server that replaces this one reads it too, and setting it again moves it. Given with since, the answer counts from the mark before it moves, which times one span after another. Default false.`
12366
+ },
12367
+ since: {
12368
+ type: "string",
12369
+ enum: ["mark"],
12370
+ ops: ["profile"],
12371
+ description: 'profile: "mark" answers only what was added after the mark that mark: true set in this run: per function the calls and self and total milliseconds since it, with the functions that added no call left out, frames and coveredMs since it, and callersOf read off the same difference. Refused when no mark was set in this run, or when the profile holds less than it did at the mark, which is a profiler started again since.'
12372
+ },
12272
12373
  limit: {
12273
12374
  type: "integer",
12274
12375
  minimum: 1,
@@ -12320,7 +12421,7 @@ var init_tool_definitions = __esm(() => {
12320
12421
  requires: []
12321
12422
  }
12322
12423
  },
12323
- answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked.",
12424
+ answer: "stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. The runtime addon's own functions, gdharness answering its tools inside the game, are not among them: their self time is runtimeAddonMs, over runtimeAddonFunctions functions. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. frames counts the frames of the profile that arrived, and the engine's totals also hold the work after the last of them, so once complete a function run every frame can show a call more than frames. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
12324
12425
  defaultOperation: "start"
12325
12426
  },
12326
12427
  {
@@ -32796,7 +32897,7 @@ var init_ajvProvider_CEoC__sr = __esm(() => {
32796
32897
  });
32797
32898
 
32798
32899
  // node_modules/@modelcontextprotocol/server/dist/shimsNode.mjs
32799
- import process9 from "node:process";
32900
+ import process10 from "node:process";
32800
32901
  var init_shimsNode = __esm(() => {
32801
32902
  init_ajvProvider_CEoC__sr();
32802
32903
  });
@@ -34610,7 +34711,7 @@ var swallowsErrorsAfterClose, StdioServerTransport = class {
34610
34711
  _readBuffer;
34611
34712
  _started = false;
34612
34713
  _closed = false;
34613
- constructor(_stdin = process9.stdin, _stdout = process9.stdout, options) {
34714
+ constructor(_stdin = process10.stdin, _stdout = process10.stdout, options) {
34614
34715
  this._stdin = _stdin;
34615
34716
  this._stdout = _stdout;
34616
34717
  this._readBuffer = new ReadBuffer({ maxBufferSize: options?.maxBufferSize });
@@ -34798,11 +34899,51 @@ var init_stdio = __esm(() => {
34798
34899
  swallowsErrorsAfterClose = Symbol("swallowsErrorsAfterClose");
34799
34900
  });
34800
34901
 
34902
+ // src/autoload-failure.ts
34903
+ import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
34904
+ import { join as join16 } from "node:path";
34905
+ function scriptsAutoloaded(projectPath) {
34906
+ const scripts = new Map;
34907
+ let settings;
34908
+ try {
34909
+ settings = parseProjectGodot(readFileSync13(join16(projectPath, "project.godot"), "utf8"));
34910
+ } catch {
34911
+ return scripts;
34912
+ }
34913
+ for (const [name, path] of autoloadPaths(settings)) {
34914
+ if (!path.endsWith(".tscn")) {
34915
+ scripts.set(`res://${path}`, name);
34916
+ continue;
34917
+ }
34918
+ const scene = join16(projectPath, path);
34919
+ if (!existsSync15(scene)) {
34920
+ continue;
34921
+ }
34922
+ try {
34923
+ for (const named of readFileSync13(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
34924
+ scripts.set(named[1] ?? "", name);
34925
+ }
34926
+ } catch {}
34927
+ }
34928
+ return scripts;
34929
+ }
34930
+ function autoloadNotCompiled(entry, autoloaded) {
34931
+ const path = NOT_COMPILED.exec(entry.text)?.[1];
34932
+ const name = path === undefined ? undefined : autoloaded.get(path);
34933
+ return path === undefined || name === undefined ? null : { name, path };
34934
+ }
34935
+ var NOT_COMPILED;
34936
+ var init_autoload_failure = __esm(() => {
34937
+ init_resources();
34938
+ init_setup();
34939
+ NOT_COMPILED = /^Failed to load script "(res:\/\/[^"]+)" with error "Compilation failed"/;
34940
+ });
34941
+
34801
34942
  // src/runtime-client.ts
34802
- import { existsSync as existsSync14, readdirSync as readdirSync9, readFileSync as readFileSync13, statSync as statSync7, unlinkSync as unlinkSync2 } from "node:fs";
34943
+ import { existsSync as existsSync16, readdirSync as readdirSync10, readFileSync as readFileSync14, statSync as statSync8, unlinkSync as unlinkSync2 } from "node:fs";
34803
34944
  import { createConnection } from "node:net";
34804
34945
  import { tmpdir as tmpdir4 } from "node:os";
34805
- import { dirname as dirname9, join as join15, resolve as resolve4 } from "node:path";
34946
+ import { dirname as dirname10, join as join17, resolve as resolve4 } from "node:path";
34806
34947
  import { setTimeout as delay } from "node:timers/promises";
34807
34948
  function runtimeDirectory(variables = process.env) {
34808
34949
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -34810,20 +34951,20 @@ function runtimeDirectory(variables = process.env) {
34810
34951
  return explicit;
34811
34952
  }
34812
34953
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
34813
- return join15(perUser ?? tmpdir4(), "gdharness");
34954
+ return join17(perUser ?? tmpdir4(), "gdharness");
34814
34955
  }
34815
34956
  function runtimeDirectories(variables = process.env) {
34816
34957
  const candidates = [runtimeDirectory(variables)];
34817
34958
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
34818
34959
  for (const base of fallbacks) {
34819
- candidates.push(join15(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34960
+ candidates.push(join17(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
34820
34961
  }
34821
34962
  return [...new Set(candidates.map((path) => resolve4(path)))];
34822
34963
  }
34823
34964
  function errorReportOf(pid, directories = runtimeDirectories()) {
34824
34965
  for (const directory of directories) {
34825
- const path = join15(directory, `runtime-${pid}.log`);
34826
- if (existsSync14(path)) {
34966
+ const path = join17(directory, `runtime-${pid}.log`);
34967
+ if (existsSync16(path)) {
34827
34968
  return path;
34828
34969
  }
34829
34970
  }
@@ -34854,7 +34995,7 @@ function reportAlreadyInTranscript(transcript, report) {
34854
34995
  function parseAnnouncement(file, pid) {
34855
34996
  let fields;
34856
34997
  try {
34857
- fields = asParams(JSON.parse(readFileSync13(file, "utf8")));
34998
+ fields = asParams(JSON.parse(readFileSync14(file, "utf8")));
34858
34999
  } catch {
34859
35000
  return { kind: "rubbish" };
34860
35001
  }
@@ -34871,7 +35012,7 @@ function parseAnnouncement(file, pid) {
34871
35012
  };
34872
35013
  let announcedAt;
34873
35014
  try {
34874
- announcedAt = statSync7(file).mtimeMs;
35015
+ announcedAt = statSync8(file).mtimeMs;
34875
35016
  } catch {}
34876
35017
  const when = announcedAt === undefined ? {} : { announcedAt };
34877
35018
  if (protocol !== RUNTIME_PROTOCOL) {
@@ -34920,7 +35061,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
34920
35061
  function wentAway(file, pid) {
34921
35062
  let project = "";
34922
35063
  try {
34923
- const fields = asParams(JSON.parse(readFileSync13(file, "utf8")));
35064
+ const fields = asParams(JSON.parse(readFileSync14(file, "utf8")));
34924
35065
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
34925
35066
  } catch {
34926
35067
  return;
@@ -34963,29 +35104,29 @@ function strangersAmong(candidates, now, startTimes = startTimesOf) {
34963
35104
  }).map((one) => one.file));
34964
35105
  }
34965
35106
  function announcedIn(directory) {
34966
- if (!existsSync14(directory)) {
35107
+ if (!existsSync16(directory)) {
34967
35108
  return [];
34968
35109
  }
34969
35110
  const found = [];
34970
35111
  const candidates = [];
34971
35112
  const entries = [];
34972
- for (const entry of readdirSync9(directory)) {
35113
+ for (const entry of readdirSync10(directory)) {
34973
35114
  const report = ERROR_REPORT_PATTERN.exec(entry);
34974
35115
  if (report) {
34975
- sweepErrorReport(join15(directory, entry), Number.parseInt(report[1] ?? "", 10));
35116
+ sweepErrorReport(join17(directory, entry), Number.parseInt(report[1] ?? "", 10));
34976
35117
  continue;
34977
35118
  }
34978
35119
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
34979
35120
  if (!match) {
34980
35121
  continue;
34981
35122
  }
34982
- const file = join15(directory, entry);
35123
+ const file = join17(directory, entry);
34983
35124
  const pid = Number.parseInt(match[1] ?? "", 10);
34984
35125
  const running = alive(pid);
34985
35126
  entries.push({ file, pid, running });
34986
35127
  if (running) {
34987
35128
  try {
34988
- candidates.push({ file, pid, writtenAt: statSync7(file).mtimeMs });
35129
+ candidates.push({ file, pid, writtenAt: statSync8(file).mtimeMs });
34989
35130
  } catch {}
34990
35131
  }
34991
35132
  }
@@ -35007,16 +35148,16 @@ function announcedIn(directory) {
35007
35148
  found.push(announced);
35008
35149
  }
35009
35150
  const held = new Set(entries.map((one) => one.file));
35010
- const here = join15(directory, ".");
35151
+ const here = join17(directory, ".");
35011
35152
  for (const file of verdicts.keys()) {
35012
- if (dirname9(file) === here && !held.has(file)) {
35153
+ if (dirname10(file) === here && !held.has(file)) {
35013
35154
  verdicts.delete(file);
35014
35155
  }
35015
35156
  }
35016
35157
  return found;
35017
35158
  }
35018
35159
  async function announcementEnded(pid, directories = runtimeDirectories()) {
35019
- const files = directories.map((directory) => join15(directory, `runtime-${pid}.json`)).filter((file) => existsSync14(file));
35160
+ const files = directories.map((directory) => join17(directory, `runtime-${pid}.json`)).filter((file) => existsSync16(file));
35020
35161
  const first = files[0];
35021
35162
  if (first === undefined) {
35022
35163
  return;
@@ -35042,7 +35183,7 @@ function sweepErrorReport(path, pid, now = Date.now()) {
35042
35183
  return;
35043
35184
  }
35044
35185
  try {
35045
- if (now - statSync7(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
35186
+ if (now - statSync8(path).mtimeMs >= ERROR_REPORT_KEEP_MS) {
35046
35187
  unlinkSync2(path);
35047
35188
  }
35048
35189
  } catch {}
@@ -35180,7 +35321,36 @@ function keepLate(id, answer) {
35180
35321
  function lateAnswer(id) {
35181
35322
  return lateAnswers.get(id);
35182
35323
  }
35183
- function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = false) {
35324
+ function placeOf(endpoint) {
35325
+ return `${dialAddress(endpoint.address)}:${endpoint.port}`;
35326
+ }
35327
+ function waitForAPlace(game, granted) {
35328
+ const line = untaken.get(game) ?? { held: 0, waiting: [] };
35329
+ untaken.set(game, line);
35330
+ const grant = () => {
35331
+ line.held += 1;
35332
+ let given = false;
35333
+ granted(() => {
35334
+ if (given) {
35335
+ return;
35336
+ }
35337
+ given = true;
35338
+ line.held -= 1;
35339
+ const next = line.waiting.shift();
35340
+ if (next !== undefined) {
35341
+ next();
35342
+ } else if (line.held === 0 && untaken.get(game) === line) {
35343
+ untaken.delete(game);
35344
+ }
35345
+ });
35346
+ };
35347
+ if (line.held < UNTAKEN_PER_GAME) {
35348
+ grant();
35349
+ } else {
35350
+ line.waiting.push(grant);
35351
+ }
35352
+ }
35353
+ function runtimeRequest(endpoint, command, params, timeoutMs, afterTheWait = "drop") {
35184
35354
  const id = nextRequestId++;
35185
35355
  const sentAt = Date.now();
35186
35356
  return new Promise((settle) => {
@@ -35188,16 +35358,32 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
35188
35358
  let late = false;
35189
35359
  let buffered = "";
35190
35360
  let welcomed = false;
35191
- const socket = createConnection({ port: endpoint.port, host: dialAddress(endpoint.address) });
35361
+ let socket = null;
35362
+ let refusals = 0;
35363
+ let redial = null;
35364
+ let giveBackThePlace = null;
35365
+ const taken = () => {
35366
+ giveBackThePlace?.();
35367
+ giveBackThePlace = null;
35368
+ };
35192
35369
  let lateTimer = null;
35193
35370
  const finish = (reply) => {
35371
+ taken();
35372
+ if (redial !== null) {
35373
+ clearTimeout(redial);
35374
+ redial = null;
35375
+ }
35194
35376
  if (late) {
35195
35377
  if (lateTimer !== null) {
35196
35378
  clearTimeout(lateTimer);
35197
35379
  }
35198
- socket.destroy();
35199
- keepLate(id, reply.ok || reply.reason === "error" ? { state: "answered", command, sentAt, answeredAt: Date.now(), reply } : { state: "never", command, sentAt, why: reply.message });
35380
+ socket?.destroy();
35200
35381
  late = false;
35382
+ if (typeof afterTheWait === "function") {
35383
+ afterTheWait(reply);
35384
+ return;
35385
+ }
35386
+ keepLate(id, reply.ok || reply.reason === "error" ? { state: "answered", command, sentAt, answeredAt: Date.now(), reply } : { state: "never", command, sentAt, why: reply.message });
35201
35387
  return;
35202
35388
  }
35203
35389
  if (done) {
@@ -35205,12 +35391,24 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
35205
35391
  }
35206
35392
  done = true;
35207
35393
  clearTimeout(timer);
35208
- socket.destroy();
35394
+ socket?.destroy();
35209
35395
  settle(reply);
35210
35396
  };
35211
35397
  const timer = setTimeout(() => {
35398
+ if (socket === null) {
35399
+ finish(refusals > 0 ? {
35400
+ ok: false,
35401
+ reason: "refused",
35402
+ message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: it refused ${refusals === 1 ? "the connection" : `all ${refusals} connections tried`} within ${timeoutMs}ms. Nothing listens there while a game is starting or shutting down, and the engine refuses connections while more are waiting for the game than the eight it queues, which several servers calling one game at once, or a long frame or a breakpoint holding it, can make it do. Nothing was sent, so nothing ran.`
35403
+ } : {
35404
+ ok: false,
35405
+ reason: "busy",
35406
+ message: `'${command}' was not sent to the game (${describe2(endpoint)}) within ${timeoutMs}ms: ${UNTAKEN_PER_GAME} connections from this server were already waiting for the game to take them, which it does between frames, so a long frame or a breakpoint holds them. Nothing was sent, so nothing ran.`
35407
+ });
35408
+ return;
35409
+ }
35212
35410
  const still = welcomed ? `The game (${describe2(endpoint)}) has the request and did not answer '${command}' within ${timeoutMs}ms.` : `The game (${describe2(endpoint)}) did not answer '${command}' within ${timeoutMs}ms and had not taken the request: its frame has not ended since it was sent, which a long frame or a breakpoint does, and the request runs when it does.`;
35213
- if (!keepListening) {
35411
+ if (afterTheWait === "drop") {
35214
35412
  finish({
35215
35413
  ok: false,
35216
35414
  reason: "busy",
@@ -35220,7 +35418,9 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
35220
35418
  }
35221
35419
  done = true;
35222
35420
  late = true;
35223
- keepLate(id, { state: "waiting", command, sentAt });
35421
+ if (afterTheWait === "keep") {
35422
+ keepLate(id, { state: "waiting", command, sentAt });
35423
+ }
35224
35424
  lateTimer = setTimeout(() => {
35225
35425
  finish({
35226
35426
  ok: false,
@@ -35230,6 +35430,15 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
35230
35430
  }, LATE_ANSWER_MS);
35231
35431
  lateTimer.unref();
35232
35432
  socket.unref();
35433
+ if (afterTheWait !== "keep") {
35434
+ settle({
35435
+ ok: false,
35436
+ reason: "busy",
35437
+ stillWithTheGame: true,
35438
+ message: `${still} Nothing was cancelled: the game runs it when it gets to it, and what it answers then is not kept, so ask again once the game answers. A game held at a breakpoint instead is one debug_state stack answers about.`
35439
+ });
35440
+ return;
35441
+ }
35233
35442
  settle({
35234
35443
  ok: false,
35235
35444
  reason: "busy",
@@ -35237,71 +35446,112 @@ function runtimeRequest(endpoint, command, params, timeoutMs, keepListening = fa
35237
35446
  message: `${still} Nothing was cancelled: a call that takes longer than the wait goes on and does everything it was asked, so read the state back rather than repeating it. runtime_invoke op result with requestId ${id} answers with its reply once it comes. A game held at a breakpoint instead is one debug_state stack answers about.`
35238
35447
  });
35239
35448
  }, timeoutMs);
35240
- socket.setEncoding("utf8");
35241
- socket.on("connect", () => {
35242
- socket.write(`${JSON.stringify({ id, command, params })}
35449
+ const open = () => {
35450
+ const opened = createConnection({ port: endpoint.port, host: dialAddress(endpoint.address) });
35451
+ socket = opened;
35452
+ opened.setEncoding("utf8");
35453
+ opened.on("connect", () => {
35454
+ opened.write(`${JSON.stringify({ id, command, params })}
35243
35455
  `);
35244
- });
35245
- socket.on("data", (chunk) => {
35246
- buffered += chunk;
35247
- let newline = buffered.indexOf(`
35456
+ });
35457
+ opened.on("data", (chunk) => {
35458
+ buffered += chunk;
35459
+ let newline = buffered.indexOf(`
35248
35460
  `);
35249
- while (newline !== -1 && (!done || late)) {
35250
- const line = buffered.slice(0, newline).trim();
35251
- buffered = buffered.slice(newline + 1);
35252
- newline = buffered.indexOf(`
35461
+ while (newline !== -1 && (!done || late)) {
35462
+ const line = buffered.slice(0, newline).trim();
35463
+ buffered = buffered.slice(newline + 1);
35464
+ newline = buffered.indexOf(`
35253
35465
  `);
35254
- if (line === "") {
35255
- continue;
35256
- }
35257
- let message;
35258
- try {
35259
- message = asParams(JSON.parse(line));
35260
- } catch {
35261
- continue;
35262
- }
35263
- if (!welcomed) {
35264
- welcomed = true;
35265
- if (readString(message, "type") === "welcome") {
35266
- const spoken = readNumber(message, "protocol");
35267
- if (spoken !== RUNTIME_PROTOCOL) {
35268
- finish({
35269
- ok: false,
35270
- reason: "protocol",
35271
- message: `The game (${describe2(endpoint)}) speaks runtime protocol ${spoken ?? "unknown"} and this server speaks ${RUNTIME_PROTOCOL}. Reinstall the runtime addon into the project.`
35272
- });
35273
- return;
35466
+ if (line === "") {
35467
+ continue;
35468
+ }
35469
+ let message;
35470
+ try {
35471
+ message = asParams(JSON.parse(line));
35472
+ } catch {
35473
+ continue;
35474
+ }
35475
+ if (!welcomed) {
35476
+ welcomed = true;
35477
+ taken();
35478
+ if (readString(message, "type") === "welcome") {
35479
+ const spoken = readNumber(message, "protocol");
35480
+ if (spoken !== RUNTIME_PROTOCOL) {
35481
+ finish({
35482
+ ok: false,
35483
+ reason: "protocol",
35484
+ message: `The game (${describe2(endpoint)}) speaks runtime protocol ${spoken ?? "unknown"} and this server speaks ${RUNTIME_PROTOCOL}. Reinstall the runtime addon into the project.`
35485
+ });
35486
+ return;
35487
+ }
35488
+ continue;
35274
35489
  }
35490
+ }
35491
+ if (message["id"] !== id) {
35275
35492
  continue;
35276
35493
  }
35494
+ if (readString(message, "type") === "error") {
35495
+ finish({
35496
+ ok: false,
35497
+ reason: "error",
35498
+ message: readString(message, "message") ?? "unknown error"
35499
+ });
35500
+ return;
35501
+ }
35502
+ finish({ ok: true, payload: message, endpoint });
35277
35503
  }
35278
- if (message["id"] !== id) {
35279
- continue;
35504
+ });
35505
+ opened.on("error", (error) => {
35506
+ if (socket !== opened) {
35507
+ return;
35280
35508
  }
35281
- if (readString(message, "type") === "error") {
35282
- finish({ ok: false, reason: "error", message: readString(message, "message") ?? "unknown error" });
35509
+ if (error.code === "ECONNREFUSED" && !done) {
35510
+ if (alive(endpoint.pid)) {
35511
+ refusals += 1;
35512
+ socket = null;
35513
+ opened.destroy();
35514
+ redial = setTimeout(() => {
35515
+ redial = null;
35516
+ open();
35517
+ }, REDIAL_MS);
35518
+ return;
35519
+ }
35520
+ finish({
35521
+ ok: false,
35522
+ reason: "refused",
35523
+ message: `The game (${describe2(endpoint)}) has ended: its port refused the connection and its process is gone.`
35524
+ });
35283
35525
  return;
35284
35526
  }
35285
- finish({ ok: true, payload: message, endpoint });
35286
- }
35287
- });
35288
- socket.on("error", (error) => {
35289
- finish({
35290
- ok: false,
35291
- reason: "refused",
35292
- message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: ${error.code ?? error.message}. It may still be starting, or be shutting down.`
35527
+ finish({
35528
+ ok: false,
35529
+ reason: "refused",
35530
+ message: `The game (${describe2(endpoint)}) is running but nothing answered on its port: ${error.code ?? error.message}. It may still be starting or be shutting down.`
35531
+ });
35293
35532
  });
35294
- });
35295
- socket.on("close", () => {
35296
- finish({
35297
- ok: false,
35298
- reason: "refused",
35299
- message: `The game (${describe2(endpoint)}) closed the connection before answering '${command}'.`
35533
+ opened.on("close", () => {
35534
+ if (socket !== opened) {
35535
+ return;
35536
+ }
35537
+ finish({
35538
+ ok: false,
35539
+ reason: "refused",
35540
+ message: `The game (${describe2(endpoint)}) closed the connection before answering '${command}'.`
35541
+ });
35300
35542
  });
35543
+ };
35544
+ waitForAPlace(placeOf(endpoint), (giveBack) => {
35545
+ if (done) {
35546
+ giveBack();
35547
+ return;
35548
+ }
35549
+ giveBackThePlace = giveBack;
35550
+ open();
35301
35551
  });
35302
35552
  });
35303
35553
  }
35304
- var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, CONSOLE_BOUNDARY = "[gdharness] runtime listening on ", ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, judging = null, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers;
35554
+ var RUNTIME_PROTOCOL = 2, ANNOUNCEMENT_PATTERN, ERROR_REPORT_PATTERN, CONSOLE_BOUNDARY = "[gdharness] runtime listening on ", ERROR_REPORT_KEEP_MS, GONE_WINDOW_MS = 120000, GONE_KEPT = 32, gone, STARTED_AFTER_ANNOUNCING_MS = 2000, CONFIRMED_FOR_MS = 60000, JUDGED_AFTER_MS = 60000, verdicts, judging = null, ENDED_WITHIN_MS = 3000, LOOK_EVERY_MS = 50, ANNOUNCE_BUDGET_MS = 5000, nextRequestId = 1, LATE_ANSWER_MS, LATE_KEPT = 50, lateAnswers, UNTAKEN_PER_GAME = 4, REDIAL_MS = 50, untaken;
35305
35555
  var init_runtime_client = __esm(() => {
35306
35556
  init_alive();
35307
35557
  init_launch();
@@ -35314,17 +35564,18 @@ var init_runtime_client = __esm(() => {
35314
35564
  verdicts = new Map;
35315
35565
  LATE_ANSWER_MS = 30 * 60000;
35316
35566
  lateAnswers = new Map;
35567
+ untaken = new Map;
35317
35568
  });
35318
35569
 
35319
35570
  // src/boot-note.ts
35320
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync8 } from "node:fs";
35321
- import { dirname as dirname10, join as join16 } from "node:path";
35571
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
35572
+ import { dirname as dirname11, join as join18 } from "node:path";
35322
35573
  function bootNotePath(projectPath) {
35323
- return join16(projectPath, ".godot", "gdharness-boot.json");
35574
+ return join18(projectPath, ".godot", "gdharness-boot.json");
35324
35575
  }
35325
35576
  function readScenes(projectPath) {
35326
35577
  try {
35327
- return asParams(asParams(JSON.parse(readFileSync14(bootNotePath(projectPath), "utf8")))["scenes"]);
35578
+ return asParams(asParams(JSON.parse(readFileSync15(bootNotePath(projectPath), "utf8")))["scenes"]);
35328
35579
  } catch {
35329
35580
  return {};
35330
35581
  }
@@ -35340,7 +35591,7 @@ function readBootNote(projectPath, scene) {
35340
35591
  function writeBootNote(projectPath, scene, announcedAfterMs) {
35341
35592
  try {
35342
35593
  const path = bootNotePath(projectPath);
35343
- mkdirSync6(dirname10(path), { recursive: true });
35594
+ mkdirSync6(dirname11(path), { recursive: true });
35344
35595
  const scenes = {
35345
35596
  ...readScenes(projectPath),
35346
35597
  [scene]: {
@@ -35366,10 +35617,10 @@ var init_boot_note = __esm(() => {
35366
35617
  });
35367
35618
 
35368
35619
  // src/breakpoint-note.ts
35369
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
35370
- import { dirname as dirname11, isAbsolute as isAbsolute3, join as join17, relative as relative3 } from "node:path";
35620
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync16, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
35621
+ import { dirname as dirname12, isAbsolute as isAbsolute4, join as join19, relative as relative3 } from "node:path";
35371
35622
  function breakpointNotePath(projectPath) {
35372
- return join17(projectPath, ".godot", "gdharness-breakpoints.json");
35623
+ return join19(projectPath, ".godot", "gdharness-breakpoints.json");
35373
35624
  }
35374
35625
  function writeBreakpointNote(projectPath, held) {
35375
35626
  const path = breakpointNotePath(projectPath);
@@ -35386,7 +35637,7 @@ function writeBreakpointNote(projectPath, held) {
35386
35637
  lines: [...one.lines]
35387
35638
  }))
35388
35639
  };
35389
- mkdirSync7(dirname11(path), { recursive: true });
35640
+ mkdirSync7(dirname12(path), { recursive: true });
35390
35641
  writeFileSync9(path, `${JSON.stringify(note, null, 2)}
35391
35642
  `, "utf8");
35392
35643
  } catch (error) {
@@ -35395,7 +35646,7 @@ function writeBreakpointNote(projectPath, held) {
35395
35646
  }
35396
35647
  function readBreakpointNote(projectPath) {
35397
35648
  try {
35398
- const parsed = JSON.parse(readFileSync15(breakpointNotePath(projectPath), "utf8"));
35649
+ const parsed = JSON.parse(readFileSync16(breakpointNotePath(projectPath), "utf8"));
35399
35650
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
35400
35651
  return [];
35401
35652
  }
@@ -35405,12 +35656,12 @@ function readBreakpointNote(projectPath) {
35405
35656
  continue;
35406
35657
  }
35407
35658
  const { script, lines: listed } = entry;
35408
- if (typeof script !== "string" || isAbsolute3(script) || script.split("/").includes("..")) {
35659
+ if (typeof script !== "string" || isAbsolute4(script) || script.split("/").includes("..")) {
35409
35660
  continue;
35410
35661
  }
35411
35662
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
35412
35663
  if (lines.length > 0) {
35413
- held.push({ scriptPath: realPathOr(join17(projectPath, script)), lines });
35664
+ held.push({ scriptPath: realPathOr(join19(projectPath, script)), lines });
35414
35665
  }
35415
35666
  }
35416
35667
  return held;
@@ -35420,7 +35671,7 @@ function readBreakpointNote(projectPath) {
35420
35671
  }
35421
35672
  function withinProject(projectPath, scriptPath) {
35422
35673
  const rel = relative3(projectPath, scriptPath);
35423
- return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
35674
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute4(rel);
35424
35675
  }
35425
35676
  var init_breakpoint_note = __esm(() => {
35426
35677
  init_errors();
@@ -35459,14 +35710,14 @@ var init_whole_file = __esm(() => {
35459
35710
  });
35460
35711
 
35461
35712
  // src/class-note.ts
35462
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync16 } from "node:fs";
35463
- import { dirname as dirname12, join as join18 } from "node:path";
35713
+ import { mkdirSync as mkdirSync8, readFileSync as readFileSync17 } from "node:fs";
35714
+ import { dirname as dirname13, join as join20 } from "node:path";
35464
35715
  function classNotePath(projectPath) {
35465
- return join18(projectPath, ".godot", "gdharness-classes.json");
35716
+ return join20(projectPath, ".godot", "gdharness-classes.json");
35466
35717
  }
35467
35718
  function readClassNote(projectPath) {
35468
35719
  try {
35469
- const fields = asParams(JSON.parse(readFileSync16(classNotePath(projectPath), "utf8")));
35720
+ const fields = asParams(JSON.parse(readFileSync17(classNotePath(projectPath), "utf8")));
35470
35721
  const rebuiltWith = readArray(fields, "rebuiltWith");
35471
35722
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
35472
35723
  return null;
@@ -35496,7 +35747,7 @@ function unfinishedScanIn(value) {
35496
35747
  function writeClassNote(projectPath, note) {
35497
35748
  const path = classNotePath(projectPath);
35498
35749
  try {
35499
- mkdirSync8(dirname12(path), { recursive: true });
35750
+ mkdirSync8(dirname13(path), { recursive: true });
35500
35751
  } catch {
35501
35752
  return;
35502
35753
  }
@@ -35508,16 +35759,16 @@ var init_class_note = __esm(() => {
35508
35759
  });
35509
35760
 
35510
35761
  // src/config-pin.ts
35511
- import { existsSync as existsSync15, readFileSync as readFileSync17 } from "node:fs";
35512
- import { join as join19 } from "node:path";
35762
+ import { existsSync as existsSync17, readFileSync as readFileSync18 } from "node:fs";
35763
+ import { join as join21 } from "node:path";
35513
35764
  function versionsNamedInConfig(projectPath) {
35514
- const config = join19(projectPath, ".mcp.json");
35515
- if (!existsSync15(config)) {
35765
+ const config = join21(projectPath, ".mcp.json");
35766
+ if (!existsSync17(config)) {
35516
35767
  return [];
35517
35768
  }
35518
35769
  let text;
35519
35770
  try {
35520
- text = readFileSync17(config, "utf8");
35771
+ text = readFileSync18(config, "utf8");
35521
35772
  JSON.parse(text);
35522
35773
  } catch {
35523
35774
  return [];
@@ -35538,26 +35789,26 @@ var init_config_pin = __esm(() => {
35538
35789
  });
35539
35790
 
35540
35791
  // src/cut-test-run.ts
35541
- import { existsSync as existsSync16, mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
35542
- import { dirname as dirname13, join as join20 } from "node:path";
35792
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync19, rmSync as rmSync9, writeFileSync as writeFileSync11 } from "node:fs";
35793
+ import { dirname as dirname14, join as join22 } from "node:path";
35543
35794
  function keptAt(projectPath) {
35544
- return join20(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35795
+ return join22(projectPath, ".godot", "gdharness-reports", "cut-run.json");
35545
35796
  }
35546
35797
  function keepCutTestRun(projectPath, run) {
35547
35798
  const path = keptAt(projectPath);
35548
35799
  try {
35549
- mkdirSync9(dirname13(path), { recursive: true });
35800
+ mkdirSync9(dirname14(path), { recursive: true });
35550
35801
  writeFileSync11(path, `${JSON.stringify(run, null, 2)}
35551
35802
  `, "utf8");
35552
35803
  } catch {}
35553
35804
  }
35554
35805
  function takeCutTestRun(projectPath) {
35555
35806
  const path = keptAt(projectPath);
35556
- if (!existsSync16(path)) {
35807
+ if (!existsSync18(path)) {
35557
35808
  return null;
35558
35809
  }
35559
35810
  try {
35560
- const run = JSON.parse(readFileSync18(path, "utf8"));
35811
+ const run = JSON.parse(readFileSync19(path, "utf8"));
35561
35812
  rmSync9(path, { force: true });
35562
35813
  return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
35563
35814
  } catch {
@@ -36370,27 +36621,27 @@ var init_dap_client = __esm(() => {
36370
36621
  import { createHash as createHash4 } from "node:crypto";
36371
36622
  import {
36372
36623
  closeSync,
36373
- existsSync as existsSync17,
36624
+ existsSync as existsSync19,
36374
36625
  mkdtempSync as mkdtempSync2,
36375
36626
  openSync,
36376
- readFileSync as readFileSync19,
36627
+ readFileSync as readFileSync20,
36377
36628
  rmSync as rmSync10,
36378
- statSync as statSync8,
36629
+ statSync as statSync9,
36379
36630
  writeFileSync as writeFileSync12,
36380
36631
  writeSync
36381
36632
  } from "node:fs";
36382
36633
  import { tmpdir as tmpdir5 } from "node:os";
36383
- import { join as join21 } from "node:path";
36634
+ import { join as join23 } from "node:path";
36384
36635
  import { setTimeout as delay3 } from "node:timers/promises";
36385
36636
  function helperAssembly(directory = tmpdir5()) {
36386
- return join21(directory, `gdharness-desktop-${DIGEST}.dll`);
36637
+ return join23(directory, `gdharness-desktop-${DIGEST}.dll`);
36387
36638
  }
36388
36639
  async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COMPILE_MS, wait = true) {
36389
36640
  const assembly = helperAssembly(directory);
36390
36641
  const lock = `${assembly}.compiling`;
36391
36642
  const until = Date.now() + budgetMs;
36392
36643
  while (Date.now() < until) {
36393
- if (existsSync17(assembly)) {
36644
+ if (existsSync19(assembly)) {
36394
36645
  return null;
36395
36646
  }
36396
36647
  let mine = false;
@@ -36408,8 +36659,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
36408
36659
  }
36409
36660
  }
36410
36661
  try {
36411
- const holder = Number(readFileSync19(lock, "utf8").trim());
36412
- const old = Date.now() - statSync8(lock).mtimeMs > budgetMs;
36662
+ const holder = Number(readFileSync20(lock, "utf8").trim());
36663
+ const old = Date.now() - statSync9(lock).mtimeMs > budgetMs;
36413
36664
  if (old || Number.isInteger(holder) && holder > 0 && !alive(holder)) {
36414
36665
  rmSync10(lock, { force: true });
36415
36666
  continue;
@@ -36423,8 +36674,8 @@ async function ensureHelperCompiled(directory = tmpdir5(), budgetMs = HELPER_COM
36423
36674
  return `the desktop helper was not compiled within ${String(budgetMs)} ms`;
36424
36675
  }
36425
36676
  async function compileHelper(directory, assembly, budgetMs) {
36426
- const scratch = mkdtempSync2(join21(directory, "gdharness-desktop-source-"));
36427
- const source = join21(scratch, "helper.cs");
36677
+ const scratch = mkdtempSync2(join23(directory, "gdharness-desktop-source-"));
36678
+ const source = join23(scratch, "helper.cs");
36428
36679
  writeFileSync12(source, SOURCE, "utf8");
36429
36680
  const literal = (path) => `'${path.replaceAll("'", "''")}'`;
36430
36681
  const script = [
@@ -36462,7 +36713,7 @@ async function compileHelper(directory, assembly, budgetMs) {
36462
36713
  });
36463
36714
  compiler.once("close", (code) => {
36464
36715
  clearTimeout(timer);
36465
- resolve(existsSync17(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
36716
+ resolve(existsSync19(assembly) ? null : `Windows PowerShell could not compile it${code === null ? " in time" : ""}: ${complained.trim() || `exit code ${String(code)}`}`);
36466
36717
  });
36467
36718
  });
36468
36719
  } finally {
@@ -36629,25 +36880,25 @@ var init_desktop = __esm(() => {
36629
36880
  // src/editor-log.ts
36630
36881
  import {
36631
36882
  closeSync as closeSync2,
36632
- existsSync as existsSync18,
36883
+ existsSync as existsSync20,
36633
36884
  mkdirSync as mkdirSync10,
36634
36885
  openSync as openSync2,
36635
- readFileSync as readFileSync20,
36886
+ readFileSync as readFileSync21,
36636
36887
  readSync,
36637
36888
  rmSync as rmSync11,
36638
- statSync as statSync9,
36889
+ statSync as statSync10,
36639
36890
  writeFileSync as writeFileSync13
36640
36891
  } from "node:fs";
36641
- import { dirname as dirname14, join as join22 } from "node:path";
36892
+ import { dirname as dirname15, join as join24 } from "node:path";
36642
36893
  function editorLogPath(projectPath) {
36643
- return join22(projectPath, ".godot", "gdharness-editor.log");
36894
+ return join24(projectPath, ".godot", "gdharness-editor.log");
36644
36895
  }
36645
36896
  function editorLogNotePath(projectPath) {
36646
- return join22(projectPath, ".godot", "gdharness-editor.json");
36897
+ return join24(projectPath, ".godot", "gdharness-editor.json");
36647
36898
  }
36648
36899
  function readEditorLogNote(projectPath) {
36649
36900
  try {
36650
- const fields = asParams(JSON.parse(readFileSync20(editorLogNotePath(projectPath), "utf8")));
36901
+ const fields = asParams(JSON.parse(readFileSync21(editorLogNotePath(projectPath), "utf8")));
36651
36902
  const pid = readNumber(fields, "pid");
36652
36903
  const startedAt = readString(fields, "startedAt");
36653
36904
  return pid !== undefined && Number.isInteger(pid) && pid > 0 && startedAt !== undefined ? { pid, startedAt } : null;
@@ -36658,7 +36909,7 @@ function readEditorLogNote(projectPath) {
36658
36909
  function clearEditorLog(projectPath) {
36659
36910
  try {
36660
36911
  const path = editorLogPath(projectPath);
36661
- mkdirSync10(dirname14(path), { recursive: true });
36912
+ mkdirSync10(dirname15(path), { recursive: true });
36662
36913
  rmSync11(path, { force: true });
36663
36914
  rmSync11(editorLogNotePath(projectPath), { force: true });
36664
36915
  } catch {}
@@ -36666,7 +36917,7 @@ function clearEditorLog(projectPath) {
36666
36917
  function writeEditorLogNote(projectPath, pid) {
36667
36918
  try {
36668
36919
  const path = editorLogNotePath(projectPath);
36669
- mkdirSync10(dirname14(path), { recursive: true });
36920
+ mkdirSync10(dirname15(path), { recursive: true });
36670
36921
  writeFileSync13(path, JSON.stringify({ pid, startedAt: new Date().toISOString() }, null, 2), "utf8");
36671
36922
  } catch {}
36672
36923
  }
@@ -36688,7 +36939,7 @@ function editorConsole(projectPath, editorPid, openedByAServer) {
36688
36939
  return { kind: "older server", notedPid: noted?.pid ?? null };
36689
36940
  }
36690
36941
  const path = editorLogPath(projectPath);
36691
- if (!existsSync18(path)) {
36942
+ if (!existsSync20(path)) {
36692
36943
  return { kind: "not written" };
36693
36944
  }
36694
36945
  const log = new GameLog;
@@ -36744,9 +36995,9 @@ function foldProgress(text) {
36744
36995
  `);
36745
36996
  }
36746
36997
  function theFirstOf(path) {
36747
- const whole = statSync9(path).size;
36998
+ const whole = statSync10(path).size;
36748
36999
  if (whole <= MOST_CONSOLE_BYTES) {
36749
- return { text: readFileSync20(path, "utf8") };
37000
+ return { text: readFileSync21(path, "utf8") };
36750
37001
  }
36751
37002
  const handle = openSync2(path, "r");
36752
37003
  try {
@@ -36907,8 +37158,8 @@ var init_editor_log = __esm(() => {
36907
37158
  });
36908
37159
 
36909
37160
  // src/rename.ts
36910
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync21, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
36911
- import { dirname as dirname15, posix as posix2 } from "node:path";
37161
+ import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync22, renameSync as renameSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync14 } from "node:fs";
37162
+ import { dirname as dirname16, posix as posix2 } from "node:path";
36912
37163
 
36913
37164
  class Script {
36914
37165
  path;
@@ -37612,25 +37863,25 @@ function rewritesOf(plan, texts, move) {
37612
37863
  }
37613
37864
  function applyRewrites(rewrites) {
37614
37865
  for (const rewrite of rewrites) {
37615
- const now = readFileSync21(rewrite.absolute, "utf8");
37866
+ const now = readFileSync22(rewrite.absolute, "utf8");
37616
37867
  if (now !== rewrite.before) {
37617
37868
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
37618
37869
  }
37619
- if (rewrite.writeTo !== rewrite.absolute && existsSync19(rewrite.writeTo)) {
37870
+ if (rewrite.writeTo !== rewrite.absolute && existsSync21(rewrite.writeTo)) {
37620
37871
  throw new Error(`${rewrite.writeTo} already exists; nothing was written`);
37621
37872
  }
37622
37873
  }
37623
37874
  const done = [];
37624
37875
  try {
37625
37876
  for (const rewrite of rewrites) {
37626
- mkdirSync11(dirname15(rewrite.writeTo), { recursive: true });
37877
+ mkdirSync11(dirname16(rewrite.writeTo), { recursive: true });
37627
37878
  const staging = `${rewrite.writeTo}.gdharness-rename`;
37628
37879
  writeFileSync14(staging, rewrite.after, "utf8");
37629
37880
  renameSync3(staging, rewrite.writeTo);
37630
37881
  let movedUid = false;
37631
37882
  if (rewrite.writeTo !== rewrite.absolute) {
37632
37883
  unlinkSync3(rewrite.absolute);
37633
- if (existsSync19(`${rewrite.absolute}.uid`)) {
37884
+ if (existsSync21(`${rewrite.absolute}.uid`)) {
37634
37885
  renameSync3(`${rewrite.absolute}.uid`, `${rewrite.writeTo}.uid`);
37635
37886
  movedUid = true;
37636
37887
  }
@@ -37676,10 +37927,10 @@ var init_rename = __esm(() => {
37676
37927
  });
37677
37928
 
37678
37929
  // src/lsp_client.ts
37679
- import { existsSync as existsSync20, realpathSync as realpathSync4 } from "node:fs";
37930
+ import { existsSync as existsSync22, realpathSync as realpathSync4 } from "node:fs";
37680
37931
  import { readFile, realpath } from "node:fs/promises";
37681
37932
  import { createConnection as createConnection3 } from "node:net";
37682
- import { dirname as dirname16, join as join23, resolve as resolve5 } from "node:path";
37933
+ import { dirname as dirname17, join as join25, resolve as resolve5 } from "node:path";
37683
37934
  import { setTimeout as delay4 } from "node:timers/promises";
37684
37935
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
37685
37936
  function initializeTimeoutMs() {
@@ -37841,7 +38092,7 @@ class GodotLSPClient {
37841
38092
  if (this.initialized) {
37842
38093
  return;
37843
38094
  }
37844
- const rootPath = this.rootPath ?? dirname16(resolve5(filePath));
38095
+ const rootPath = this.rootPath ?? dirname17(resolve5(filePath));
37845
38096
  await this.initialize(rootPath);
37846
38097
  }
37847
38098
  async sendRequest(method, params, timeoutMs = this.timeouts.requestMs) {
@@ -38357,7 +38608,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
38357
38608
  } catch {
38358
38609
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
38359
38610
  }
38360
- if (!existsSync20(join23(projectPath, "project.godot"))) {
38611
+ if (!existsSync22(join25(projectPath, "project.godot"))) {
38361
38612
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
38362
38613
  }
38363
38614
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -38517,8 +38768,8 @@ var init_lsp_client = __esm(() => {
38517
38768
  });
38518
38769
 
38519
38770
  // src/rename-symbol.ts
38520
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, readdirSync as readdirSync10, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
38521
- import { basename as basename6, dirname as dirname17, join as join24, relative as relative4, resolve as resolve6 } from "node:path";
38771
+ import { existsSync as existsSync23, mkdirSync as mkdirSync12, readdirSync as readdirSync11, realpathSync as realpathSync5, rmSync as rmSync12, writeFileSync as writeFileSync15 } from "node:fs";
38772
+ import { basename as basename7, dirname as dirname18, join as join26, relative as relative4, resolve as resolve6 } from "node:path";
38522
38773
  function resourcePathOf(projectPath, absolute) {
38523
38774
  const inside = relative4(onDiskSpelling(projectPath), onDiskSpelling(absolute));
38524
38775
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -38531,13 +38782,13 @@ function onDiskSpelling(path) {
38531
38782
  let at = resolve6(path);
38532
38783
  for (;; ) {
38533
38784
  try {
38534
- return join24(realpathSync5.native(at), ...missing.reverse());
38785
+ return join26(realpathSync5.native(at), ...missing.reverse());
38535
38786
  } catch {
38536
- const parent = dirname17(at);
38787
+ const parent = dirname18(at);
38537
38788
  if (parent === at) {
38538
38789
  return resolve6(path);
38539
38790
  }
38540
- missing.push(basename6(at));
38791
+ missing.push(basename7(at));
38541
38792
  at = parent;
38542
38793
  }
38543
38794
  }
@@ -38584,7 +38835,7 @@ async function renameSymbol(request, services) {
38584
38835
  reason: `newScriptPath must be a .gd path inside the project, and ${request.newScriptPath} is not.`
38585
38836
  };
38586
38837
  }
38587
- if (existsSync21(request.newScriptPath)) {
38838
+ if (existsSync23(request.newScriptPath)) {
38588
38839
  return { ok: false, reason: `${moveTo} already exists, so the script cannot be moved there.` };
38589
38840
  }
38590
38841
  }
@@ -38760,7 +39011,7 @@ async function renameSymbol(request, services) {
38760
39011
  scripts,
38761
39012
  moves: new Map([[declaringPath, moveTo]]),
38762
39013
  named: [declaringPath],
38763
- exists: (path) => existsSync21(`${projectPath}/${path.slice("res://".length)}`)
39014
+ exists: (path) => existsSync23(`${projectPath}/${path.slice("res://".length)}`)
38764
39015
  }, plan);
38765
39016
  }
38766
39017
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
@@ -38959,15 +39210,15 @@ ${mention.kind}`;
38959
39210
  };
38960
39211
  }
38961
39212
  function writeLeftAloneDetail(projectPath, symbol, detail) {
38962
- const directory = join24(projectPath, ".godot", "gdharness-renames");
39213
+ const directory = join26(projectPath, ".godot", "gdharness-renames");
38963
39214
  mkdirSync12(directory, { recursive: true });
38964
39215
  detailWrites += 1;
38965
- const path = join24(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
39216
+ const path = join26(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
38966
39217
  writeFileSync15(path, `${JSON.stringify(detail, null, 2)}
38967
39218
  `, "utf8");
38968
- const kept = readdirSync10(directory).filter((name) => name.endsWith(".json")).sort().reverse();
39219
+ const kept = readdirSync11(directory).filter((name) => name.endsWith(".json")).sort().reverse();
38969
39220
  for (const old of kept.slice(10)) {
38970
- rmSync12(join24(directory, old), { force: true });
39221
+ rmSync12(join26(directory, old), { force: true });
38971
39222
  }
38972
39223
  return path;
38973
39224
  }
@@ -39008,25 +39259,25 @@ var init_rename_symbol = __esm(() => {
39008
39259
 
39009
39260
  // src/file-move.ts
39010
39261
  import {
39011
- existsSync as existsSync22,
39262
+ existsSync as existsSync24,
39012
39263
  mkdirSync as mkdirSync13,
39013
- readdirSync as readdirSync11,
39014
- readFileSync as readFileSync22,
39264
+ readdirSync as readdirSync12,
39265
+ readFileSync as readFileSync23,
39015
39266
  renameSync as renameSync4,
39016
- statSync as statSync10,
39267
+ statSync as statSync11,
39017
39268
  writeFileSync as writeFileSync16
39018
39269
  } from "node:fs";
39019
- import { basename as basename7, dirname as dirname18, extname as extname3, join as join25, relative as relative5 } from "node:path";
39270
+ import { basename as basename8, dirname as dirname19, extname as extname3, join as join27, relative as relative5 } from "node:path";
39020
39271
  function filesUnder(directory) {
39021
- return readdirSync11(directory, { withFileTypes: true }).flatMap((entry) => {
39022
- const path = join25(directory, entry.name);
39272
+ return readdirSync12(directory, { withFileTypes: true }).flatMap((entry) => {
39273
+ const path = join27(directory, entry.name);
39023
39274
  return entry.isDirectory() ? filesUnder(path) : [path];
39024
39275
  });
39025
39276
  }
39026
39277
  function sameFile(one, other) {
39027
39278
  try {
39028
- const a = statSync10(one);
39029
- const b = statSync10(other);
39279
+ const a = statSync11(one);
39280
+ const b = statSync11(other);
39030
39281
  return a.ino === b.ino && a.dev === b.dev;
39031
39282
  } catch {
39032
39283
  return false;
@@ -39040,8 +39291,8 @@ function unseenByTheEngine(projectPath, path) {
39040
39291
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
39041
39292
  }
39042
39293
  if (at < steps.length - 1) {
39043
- directory = join25(directory, step);
39044
- if (existsSync22(join25(directory, ".gdignore"))) {
39294
+ directory = join27(directory, step);
39295
+ if (existsSync24(join27(directory, ".gdignore"))) {
39045
39296
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
39046
39297
  }
39047
39298
  }
@@ -39053,21 +39304,21 @@ function installedAddonOf(projectPath, path) {
39053
39304
  if (steps[0] !== "addons" || steps[1] === undefined) {
39054
39305
  return null;
39055
39306
  }
39056
- return existsSync22(join25(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
39307
+ return existsSync24(join27(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
39057
39308
  }
39058
39309
  function binaryResources(projectPath) {
39059
39310
  const found = [];
39060
39311
  const visit = (directory, prefix) => {
39061
- if (existsSync22(join25(directory, ".gdignore"))) {
39312
+ if (existsSync24(join27(directory, ".gdignore"))) {
39062
39313
  return;
39063
39314
  }
39064
- for (const entry of readdirSync11(directory, { withFileTypes: true })) {
39315
+ for (const entry of readdirSync12(directory, { withFileTypes: true })) {
39065
39316
  if (entry.name.startsWith(".")) {
39066
39317
  continue;
39067
39318
  }
39068
39319
  const path = `${prefix}${entry.name}`;
39069
39320
  if (entry.isDirectory()) {
39070
- visit(join25(directory, entry.name), `${path}/`);
39321
+ visit(join27(directory, entry.name), `${path}/`);
39071
39322
  } else if (/\.(scn|res)$/i.test(entry.name)) {
39072
39323
  found.push(path);
39073
39324
  }
@@ -39077,7 +39328,7 @@ function binaryResources(projectPath) {
39077
39328
  return found;
39078
39329
  }
39079
39330
  function targetPathOf(projectPath, absolute) {
39080
- const inside = relative5(onDiskSpelling(projectPath), join25(onDiskSpelling(dirname18(absolute)), basename7(absolute)));
39331
+ const inside = relative5(onDiskSpelling(projectPath), join27(onDiskSpelling(dirname19(absolute)), basename8(absolute)));
39081
39332
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
39082
39333
  return null;
39083
39334
  }
@@ -39095,13 +39346,13 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39095
39346
  if (to === null) {
39096
39347
  return { ok: false, reason: `newPath must be a path inside the project, and ${absoluteTo} is not.` };
39097
39348
  }
39098
- if (!existsSync22(absoluteFrom)) {
39349
+ if (!existsSync24(absoluteFrom)) {
39099
39350
  return { ok: false, reason: `There is nothing at ${from} to move.` };
39100
39351
  }
39101
39352
  if (from === to) {
39102
39353
  return { ok: false, reason: `${from} is already there; nothing to move.` };
39103
39354
  }
39104
- const isDirectory = statSync10(absoluteFrom).isDirectory();
39355
+ const isDirectory = statSync11(absoluteFrom).isDirectory();
39105
39356
  if (from === "res://project.godot") {
39106
39357
  return {
39107
39358
  ok: false,
@@ -39124,7 +39375,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39124
39375
  if (isDirectory && to.startsWith(`${from}/`)) {
39125
39376
  return { ok: false, reason: `${from} cannot be moved into itself.` };
39126
39377
  }
39127
- if (existsSync22(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
39378
+ if (existsSync24(absoluteTo) && !sameFile(absoluteFrom, absoluteTo)) {
39128
39379
  return { ok: false, reason: `${to} already exists, so nothing was moved there.` };
39129
39380
  }
39130
39381
  const unseen = unseenByTheEngine(projectPath, to);
@@ -39132,7 +39383,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39132
39383
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
39133
39384
  }
39134
39385
  if (!isDirectory) {
39135
- const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync22(join25(projectPath, path.slice("res://".length))));
39386
+ const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync24(join27(projectPath, path.slice("res://".length))));
39136
39387
  if (owner !== undefined && owner !== null) {
39137
39388
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
39138
39389
  }
@@ -39154,7 +39405,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39154
39405
  } else {
39155
39406
  moves.set(from, to);
39156
39407
  for (const sidecar of SIDECARS) {
39157
- if (existsSync22(`${absoluteFrom}${sidecar}`)) {
39408
+ if (existsSync24(`${absoluteFrom}${sidecar}`)) {
39158
39409
  moves.set(`${from}${sidecar}`, `${to}${sidecar}`);
39159
39410
  }
39160
39411
  }
@@ -39166,12 +39417,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39166
39417
  scripts,
39167
39418
  moves,
39168
39419
  named: [from],
39169
- exists: (path) => existsSync22(join25(projectPath, path.slice("res://".length)))
39420
+ exists: (path) => existsSync24(join27(projectPath, path.slice("res://".length)))
39170
39421
  }, { edits: [], mentions: [] });
39171
39422
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
39172
39423
  const binaries = [];
39173
39424
  for (const path of binaryResources(projectPath)) {
39174
- const bytes = readFileSync22(join25(projectPath, path.slice("res://".length)));
39425
+ const bytes = readFileSync23(join27(projectPath, path.slice("res://".length)));
39175
39426
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
39176
39427
  if (names !== undefined) {
39177
39428
  binaries.push({
@@ -39190,7 +39441,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39190
39441
  }));
39191
39442
  const left = leftAloneReport(mentions);
39192
39443
  const rewrites = rewritesOf(plan, texts, null);
39193
- const absoluteOf = (path) => join25(projectPath, path.slice("res://".length));
39444
+ const absoluteOf = (path) => join27(projectPath, path.slice("res://".length));
39194
39445
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
39195
39446
  const moved = [...moves].map(([source, target]) => {
39196
39447
  const rewrite = rewritten.get(source);
@@ -39232,24 +39483,24 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
39232
39483
  }
39233
39484
  function applyFileMove(outcome) {
39234
39485
  for (const rewrite of outcome.inPlace) {
39235
- if (readFileSync22(rewrite.absolute, "utf8") !== rewrite.before) {
39486
+ if (readFileSync23(rewrite.absolute, "utf8") !== rewrite.before) {
39236
39487
  throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
39237
39488
  }
39238
39489
  }
39239
39490
  for (const file of outcome.moved) {
39240
- if (!existsSync22(file.absoluteFrom)) {
39491
+ if (!existsSync24(file.absoluteFrom)) {
39241
39492
  throw new Error(`${file.from} is gone from disk; nothing was written`);
39242
39493
  }
39243
- if (file.text !== null && readFileSync22(file.absoluteFrom, "utf8") !== file.text.before) {
39494
+ if (file.text !== null && readFileSync23(file.absoluteFrom, "utf8") !== file.text.before) {
39244
39495
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
39245
39496
  }
39246
- if (existsSync22(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
39497
+ if (existsSync24(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
39247
39498
  throw new Error(`${file.to} already exists; nothing was written`);
39248
39499
  }
39249
39500
  }
39250
39501
  const undo = [];
39251
39502
  const rename = (source, target) => {
39252
- mkdirSync13(dirname18(target), { recursive: true });
39503
+ mkdirSync13(dirname19(target), { recursive: true });
39253
39504
  renameSync4(source, target);
39254
39505
  undo.push(() => {
39255
39506
  renameSync4(target, source);
@@ -43435,25 +43686,25 @@ var DEBUG_PREFIX = "debug ";
43435
43686
  var init_keeper_channel = () => {};
43436
43687
 
43437
43688
  // src/lifecycle-log.ts
43438
- import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync11 } from "node:fs";
43439
- import { dirname as dirname19, join as join26 } from "node:path";
43440
- import process10 from "node:process";
43689
+ import { appendFileSync, mkdirSync as mkdirSync14, renameSync as renameSync5, statSync as statSync12 } from "node:fs";
43690
+ import { dirname as dirname20, join as join28 } from "node:path";
43691
+ import process11 from "node:process";
43441
43692
  function lifecycleLogPath() {
43442
- return join26(runtimeDirectory(), "servers.log");
43693
+ return join28(runtimeDirectory(), "servers.log");
43443
43694
  }
43444
43695
  function writeLifecycle(event, fields = {}) {
43445
43696
  const path = lifecycleLogPath();
43446
43697
  try {
43447
- mkdirSync14(dirname19(path), { recursive: true });
43448
- if ((statSync11(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
43698
+ mkdirSync14(dirname20(path), { recursive: true });
43699
+ if ((statSync12(path, { throwIfNoEntry: false })?.size ?? 0) > LIFECYCLE_LOG_LIMIT) {
43449
43700
  try {
43450
43701
  renameSync5(path, `${path}.1`);
43451
43702
  } catch {}
43452
43703
  }
43453
43704
  const line = {
43454
43705
  at: new Date().toISOString(),
43455
- pid: process10.pid,
43456
- ppid: process10.ppid,
43706
+ pid: process11.pid,
43707
+ ppid: process11.ppid,
43457
43708
  version: SERVER_VERSION,
43458
43709
  event
43459
43710
  };
@@ -43759,15 +44010,15 @@ var init_prompts = __esm(() => {
43759
44010
  });
43760
44011
 
43761
44012
  // src/restart-note.ts
43762
- import { mkdirSync as mkdirSync15, readFileSync as readFileSync23, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
43763
- import { dirname as dirname20, join as join27 } from "node:path";
44013
+ import { mkdirSync as mkdirSync15, readFileSync as readFileSync24, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
44014
+ import { dirname as dirname21, join as join29 } from "node:path";
43764
44015
  function restartNotePath(projectPath) {
43765
- return join27(projectPath, ".godot", "gdharness-restart.json");
44016
+ return join29(projectPath, ".godot", "gdharness-restart.json");
43766
44017
  }
43767
44018
  function noteRestartBegun(note) {
43768
44019
  const path = restartNotePath(note.projectPath);
43769
44020
  try {
43770
- mkdirSync15(dirname20(path), { recursive: true });
44021
+ mkdirSync15(dirname21(path), { recursive: true });
43771
44022
  writeFileSync17(path, `${JSON.stringify(note, null, 2)}
43772
44023
  `, "utf8");
43773
44024
  } catch (error) {
@@ -43776,7 +44027,7 @@ function noteRestartBegun(note) {
43776
44027
  }
43777
44028
  function restartOwed(projectPath) {
43778
44029
  try {
43779
- const parsed = JSON.parse(readFileSync23(restartNotePath(projectPath), "utf8"));
44030
+ const parsed = JSON.parse(readFileSync24(restartNotePath(projectPath), "utf8"));
43780
44031
  if (typeof parsed !== "object" || parsed === null) {
43781
44032
  return null;
43782
44033
  }
@@ -43802,23 +44053,23 @@ var init_restart_note = __esm(() => {
43802
44053
 
43803
44054
  // src/run-record.ts
43804
44055
  import { createHash as createHash5 } from "node:crypto";
43805
- import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync12, readFileSync as readFileSync24, realpathSync as realpathSync6, rmSync as rmSync14, statSync as statSync12 } from "node:fs";
43806
- import { basename as basename8, dirname as dirname21, join as join28, resolve as resolve7 } from "node:path";
44056
+ import { mkdirSync as mkdirSync16, openSync as openSync3, readdirSync as readdirSync13, readFileSync as readFileSync25, realpathSync as realpathSync6, rmSync as rmSync14, statSync as statSync13 } from "node:fs";
44057
+ import { basename as basename9, dirname as dirname22, join as join30, resolve as resolve7 } from "node:path";
43807
44058
  function runsDirectory() {
43808
- return join28(runtimeDirectory(), "runs");
44059
+ return join30(runtimeDirectory(), "runs");
43809
44060
  }
43810
44061
  function realAsFarAsItExists(path) {
43811
44062
  const missing = [];
43812
44063
  let at = path;
43813
44064
  for (;; ) {
43814
44065
  try {
43815
- return join28(realpathSync6.native(at), ...missing);
44066
+ return join30(realpathSync6.native(at), ...missing);
43816
44067
  } catch {
43817
- const parent = dirname21(at);
44068
+ const parent = dirname22(at);
43818
44069
  if (parent === at) {
43819
44070
  return path;
43820
44071
  }
43821
- missing.unshift(basename8(at));
44072
+ missing.unshift(basename9(at));
43822
44073
  at = parent;
43823
44074
  }
43824
44075
  }
@@ -43829,10 +44080,10 @@ function projectKey(projectPath) {
43829
44080
  return createHash5("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
43830
44081
  }
43831
44082
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
43832
- return join28(directory, "runs", `note-${projectKey(projectPath)}.json`);
44083
+ return join30(directory, "runs", `note-${projectKey(projectPath)}.json`);
43833
44084
  }
43834
44085
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
43835
- return join28(directory, "runs", `latest-${projectKey(servedBy)}.json`);
44086
+ return join30(directory, "runs", `latest-${projectKey(servedBy)}.json`);
43836
44087
  }
43837
44088
  function writeLatestRun(servedBy, latest) {
43838
44089
  try {
@@ -43842,7 +44093,7 @@ function writeLatestRun(servedBy, latest) {
43842
44093
  }
43843
44094
  function readLatestRun(servedBy, directory = runtimeDirectory()) {
43844
44095
  try {
43845
- const fields = JSON.parse(readFileSync24(latestRunPath(servedBy, directory), "utf8"));
44096
+ const fields = JSON.parse(readFileSync25(latestRunPath(servedBy, directory), "utf8"));
43846
44097
  if (typeof fields !== "object" || fields === null) {
43847
44098
  return null;
43848
44099
  }
@@ -43857,17 +44108,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
43857
44108
  }
43858
44109
  function openTranscript(startedAt) {
43859
44110
  mkdirSync16(runsDirectory(), { recursive: true });
43860
- const path = join28(runsDirectory(), `run-${startedAt}.log`);
44111
+ const path = join30(runsDirectory(), `run-${startedAt}.log`);
43861
44112
  return { path, fd: openSync3(path, "a") };
43862
44113
  }
43863
44114
  function profilePath(startedAt) {
43864
- return join28(runsDirectory(), `run-${startedAt}.profile.json`);
44115
+ return join30(runsDirectory(), `run-${startedAt}.profile.json`);
44116
+ }
44117
+ function profileMarkPath(startedAt) {
44118
+ return join30(runsDirectory(), `run-${startedAt}.profile-mark.json`);
43865
44119
  }
43866
44120
  function noteIsAbout(note, playing) {
43867
44121
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
43868
44122
  }
43869
44123
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
43870
- return join28(directory, "runs", `editor-${projectKey(projectPath)}.json`);
44124
+ return join30(directory, "runs", `editor-${projectKey(projectPath)}.json`);
43871
44125
  }
43872
44126
  function writeEditorRunNote(note) {
43873
44127
  mkdirSync16(runsDirectory(), { recursive: true });
@@ -43878,7 +44132,7 @@ function writeEditorRunNote(note) {
43878
44132
  }
43879
44133
  function readEditorRunNote(projectPath) {
43880
44134
  for (const directory of runtimeDirectories()) {
43881
- const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join28(directory, "runs", "editor-run.json"));
44135
+ const note = editorNoteAt(editorNotePath(projectPath, directory)) ?? editorNoteAt(join30(directory, "runs", "editor-run.json"));
43882
44136
  if (note !== null && isSameDirectory(note.projectPath, projectPath)) {
43883
44137
  return note;
43884
44138
  }
@@ -43888,7 +44142,7 @@ function readEditorRunNote(projectPath) {
43888
44142
  function editorNoteAt(path) {
43889
44143
  let parsed;
43890
44144
  try {
43891
- parsed = JSON.parse(readFileSync24(path, "utf8"));
44145
+ parsed = JSON.parse(readFileSync25(path, "utf8"));
43892
44146
  } catch {
43893
44147
  return null;
43894
44148
  }
@@ -43912,7 +44166,7 @@ function readRunRecord(projectPath) {
43912
44166
  if (own !== null) {
43913
44167
  return own;
43914
44168
  }
43915
- const shared = recordAt(join28(directory, "runs", SHARED_NOTE));
44169
+ const shared = recordAt(join30(directory, "runs", SHARED_NOTE));
43916
44170
  if (shared !== null && shared.projectPath !== "" && isSameDirectory(shared.projectPath, projectPath)) {
43917
44171
  return shared;
43918
44172
  }
@@ -43924,13 +44178,13 @@ function everyRunRecord() {
43924
44178
  for (const directory of runtimeDirectories()) {
43925
44179
  let entries;
43926
44180
  try {
43927
- entries = readdirSync12(join28(directory, "runs"));
44181
+ entries = readdirSync13(join30(directory, "runs"));
43928
44182
  } catch {
43929
44183
  continue;
43930
44184
  }
43931
44185
  for (const entry of entries) {
43932
44186
  if (entry === SHARED_NOTE || entry.startsWith("note-") && entry.endsWith(".json")) {
43933
- const record = recordAt(join28(directory, "runs", entry));
44187
+ const record = recordAt(join30(directory, "runs", entry));
43934
44188
  if (record !== null) {
43935
44189
  found.push(record);
43936
44190
  }
@@ -43942,7 +44196,7 @@ function everyRunRecord() {
43942
44196
  function recordAt(path) {
43943
44197
  let parsed;
43944
44198
  try {
43945
- parsed = JSON.parse(readFileSync24(path, "utf8"));
44199
+ parsed = JSON.parse(readFileSync25(path, "utf8"));
43946
44200
  } catch {
43947
44201
  return null;
43948
44202
  }
@@ -43995,7 +44249,7 @@ async function runningAs(pid) {
43995
44249
  const began = await startedAt(pid, null);
43996
44250
  const when = began === null ? {} : { startedAt: began };
43997
44251
  if (process.platform === "linux") {
43998
- const raw = readFileSync24(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
44252
+ const raw = readFileSync25(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
43999
44253
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
44000
44254
  }
44001
44255
  const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
@@ -44099,7 +44353,7 @@ function whyNotTheRun(record, running, needed) {
44099
44353
  if (after > SAME_RUN_WINDOW_MS) {
44100
44354
  return `that process started ${Math.round(after / 1000)} seconds after the run had`;
44101
44355
  }
44102
- const engine = record.command === undefined ? null : basename8(record.command);
44356
+ const engine = record.command === undefined ? null : basename9(record.command);
44103
44357
  const said = process.platform === "win32" ? running.text.toLowerCase() : running.text;
44104
44358
  const wanted = engine === null || process.platform !== "win32" ? engine : engine.toLowerCase();
44105
44359
  if (wanted !== null && !said.includes(wanted)) {
@@ -44138,7 +44392,7 @@ function judgeAnnouncedGame(running, projectPath, announcedAt) {
44138
44392
  }
44139
44393
  function clearRunRecord(projectPath) {
44140
44394
  for (const directory of runtimeDirectories()) {
44141
- const shared = join28(directory, "runs", SHARED_NOTE);
44395
+ const shared = join30(directory, "runs", SHARED_NOTE);
44142
44396
  const sharedRecord = recordAt(shared);
44143
44397
  const ours = sharedRecord !== null && sharedRecord.projectPath !== "" && isSameDirectory(sharedRecord.projectPath, projectPath);
44144
44398
  for (const path of [runRecordPath(projectPath, directory), ...ours ? [shared] : []]) {
@@ -44150,7 +44404,7 @@ function clearRunRecord(projectPath) {
44150
44404
  }
44151
44405
  function ageMs(path, now) {
44152
44406
  try {
44153
- return now - statSync12(path).mtimeMs;
44407
+ return now - statSync13(path).mtimeMs;
44154
44408
  } catch {
44155
44409
  return 0;
44156
44410
  }
@@ -44159,20 +44413,21 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
44159
44413
  const directory = runsDirectory();
44160
44414
  const kept = new Set(everyRunRecord().flatMap((record) => [
44161
44415
  record.transcript,
44416
+ profileMarkPath(record.startedAt),
44162
44417
  ...record.profile === undefined ? [] : [record.profile]
44163
44418
  ]));
44164
44419
  let entries;
44165
44420
  try {
44166
- entries = readdirSync12(directory);
44421
+ entries = readdirSync13(directory);
44167
44422
  } catch {
44168
44423
  return;
44169
44424
  }
44170
44425
  for (const entry of entries) {
44171
- const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json"));
44426
+ const transcript = entry.startsWith("run-") && (entry.endsWith(".log") || entry.endsWith(".profile.json") || entry.endsWith(".profile-mark.json"));
44172
44427
  if (!transcript && !entry.endsWith(".partial")) {
44173
44428
  continue;
44174
44429
  }
44175
- const path = join28(directory, entry);
44430
+ const path = join30(directory, entry);
44176
44431
  if (kept.has(path) || ageMs(path, now) < keepMs) {
44177
44432
  continue;
44178
44433
  }
@@ -44193,7 +44448,7 @@ var init_run_record = __esm(() => {
44193
44448
  });
44194
44449
 
44195
44450
  // src/script-origins.ts
44196
- import { join as join29 } from "node:path";
44451
+ import { join as join31 } from "node:path";
44197
44452
  function unresolvedClassIn(message) {
44198
44453
  return UNRESOLVED.exec(message)?.[1] ?? null;
44199
44454
  }
@@ -44230,6 +44485,12 @@ async function originsOf(errors, declared, check) {
44230
44485
  }
44231
44486
  return { origins, unconfirmed };
44232
44487
  }
44488
+ function compileErrorsIn(entries) {
44489
+ return entries.flatMap((entry) => {
44490
+ const at = entry.severity === "error" ? reloadedAt(entry) : null;
44491
+ return at === null ? [] : [{ ...at, message: entry.text }];
44492
+ });
44493
+ }
44233
44494
  function reloadedAt(entry) {
44234
44495
  for (const line of entry.detail) {
44235
44496
  const at = RELOADED_AT.exec(line.trim());
@@ -44246,7 +44507,7 @@ function checkWithEngine(godotPath, projectPath) {
44246
44507
  const ran = await runEngine(godotPath, [
44247
44508
  "--headless",
44248
44509
  "--log-file",
44249
- join29(logDir, "engine.log"),
44510
+ join31(logDir, "engine.log"),
44250
44511
  "--path",
44251
44512
  projectPath,
44252
44513
  "--check-only",
@@ -44291,10 +44552,10 @@ var init_script_origins = __esm(() => {
44291
44552
  });
44292
44553
 
44293
44554
  // src/script-profile.ts
44294
- import { readFileSync as readFileSync25 } from "node:fs";
44555
+ import { readFileSync as readFileSync26 } from "node:fs";
44295
44556
  function readProfileFile(path) {
44296
44557
  try {
44297
- const parsed = JSON.parse(readFileSync25(path, "utf8"));
44558
+ const parsed = JSON.parse(readFileSync26(path, "utf8"));
44298
44559
  return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
44299
44560
  } catch {
44300
44561
  return null;
@@ -44322,12 +44583,16 @@ function signatureParts(signature) {
44322
44583
  function profiledFunctions(totals, limit, sourceOf = () => null) {
44323
44584
  const perCallUs = perCallCostOf(totals);
44324
44585
  const rows = rowsOf(totals, sourceOf).map((row) => withOverhead(row, perCallUs));
44325
- const scriptMs = ms(Object.entries(totals.functions).reduce((sum, [signature, one]) => sum + (one.calls > 0 && !isCalibration(signature) ? one.selfSeconds : 0), 0));
44586
+ const called = Object.entries(totals.functions).filter(([signature, one]) => one.calls > 0 && !isCalibration(signature));
44587
+ const addon = called.filter(([signature]) => isRuntimeAddon(signature));
44588
+ const selfOf = (entries) => ms(entries.reduce((sum, [, one]) => sum + one.selfSeconds, 0));
44326
44589
  return {
44327
44590
  functions: rows.slice(0, limit).map((row) => placedLambda(row, sourceOf)),
44328
44591
  omitted: Math.max(0, rows.length - limit),
44329
- scriptMs,
44592
+ scriptMs: selfOf(called.filter(([signature]) => !isRuntimeAddon(signature))),
44330
44593
  counted: rows.length,
44594
+ addonMs: selfOf(addon),
44595
+ addonFunctions: addon.length,
44331
44596
  superCallers: rows.filter((row) => row.selfIncludesSuper === true).length,
44332
44597
  perCallUs,
44333
44598
  overheadDominated: rows.filter((row) => row.overheadMs !== undefined).length,
@@ -44335,10 +44600,59 @@ function profiledFunctions(totals, limit, sourceOf = () => null) {
44335
44600
  overheadReach: rows.findLastIndex((row) => row.overheadMs !== undefined) + 1
44336
44601
  };
44337
44602
  }
44603
+ function isRuntimeAddon(signature) {
44604
+ return signatureParts(signature).script.startsWith(RUNTIME_ADDON2);
44605
+ }
44338
44606
  function isCalibration(signature) {
44339
44607
  const parts = signatureParts(signature);
44340
44608
  return parts.script.endsWith(RUNTIME_SCRIPT) && (parts.function === PROFILER_PROBE || parts.function === PROFILER_CALIBRATOR);
44341
44609
  }
44610
+ function writeProfileMark(path, mark) {
44611
+ return writeWhole(path, JSON.stringify(mark));
44612
+ }
44613
+ function readProfileMark(path) {
44614
+ try {
44615
+ const parsed = JSON.parse(readFileSync26(path, "utf8"));
44616
+ return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
44617
+ } catch {
44618
+ return null;
44619
+ }
44620
+ }
44621
+ function totalsSince(now, mark) {
44622
+ if (now.frames < mark.frames || now.startedAt !== mark.startedAt) {
44623
+ return null;
44624
+ }
44625
+ let shrunk = 0;
44626
+ const functions = {};
44627
+ for (const [signature, sum] of Object.entries(now.functions)) {
44628
+ const before = mark.functions[signature];
44629
+ if (isCalibration(signature) || before === undefined) {
44630
+ functions[signature] = sum;
44631
+ continue;
44632
+ }
44633
+ const added = {
44634
+ calls: sum.calls - before.calls,
44635
+ selfSeconds: sum.selfSeconds - before.selfSeconds,
44636
+ totalSeconds: sum.totalSeconds - before.totalSeconds
44637
+ };
44638
+ if (added.calls < 0 || added.selfSeconds < 0 || added.totalSeconds < 0) {
44639
+ shrunk += 1;
44640
+ } else if (added.calls > 0) {
44641
+ functions[signature] = added;
44642
+ }
44643
+ }
44644
+ shrunk += Object.keys(mark.functions).filter((signature) => now.functions[signature] === undefined && (mark.functions[signature]?.calls ?? 0) > 0).length;
44645
+ return {
44646
+ totals: {
44647
+ ...now,
44648
+ frames: now.frames - mark.frames,
44649
+ unreadable: Math.max(0, now.unreadable - mark.unreadable),
44650
+ ...now.cappedFrames === undefined ? {} : { cappedFrames: Math.max(0, now.cappedFrames - (mark.cappedFrames ?? 0)) },
44651
+ functions
44652
+ },
44653
+ shrunk
44654
+ };
44655
+ }
44342
44656
  function perCallCostOf(totals) {
44343
44657
  for (const [signature, sum] of Object.entries(totals.functions)) {
44344
44658
  const parts = signatureParts(signature);
@@ -44364,7 +44678,7 @@ function rowsOf(totals, sourceOf) {
44364
44678
  }
44365
44679
  return codes.get(script) ?? null;
44366
44680
  };
44367
- return Object.entries(totals.functions).filter(([signature, sum]) => sum.calls > 0 && !isCalibration(signature)).map(([signature, sum]) => {
44681
+ return Object.entries(totals.functions).filter(([signature, sum]) => sum.calls > 0 && !isRuntimeAddon(signature) && !isCalibration(signature)).map(([signature, sum]) => {
44368
44682
  const parts = signatureParts(signature);
44369
44683
  const code = codeOfScript(parts.script);
44370
44684
  const property = propertyOf(parts.function);
@@ -44440,7 +44754,7 @@ function callersOf(totals, name, scripts) {
44440
44754
  property
44441
44755
  };
44442
44756
  }
44443
- var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START", ms = (seconds) => Math.round(seconds * 1e6) / 1000, RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd", PROFILER_PROBE = "_profiler_probe", PROFILER_CALIBRATOR = "_calibrate_the_profiler", CALL_SITES = 40;
44757
+ var QUEUE_SETTING = "limits/debugger/max_queued_messages", QUEUE_DEFAULT = 2048, QUEUE_HEADROOM = 512, UNNAMED_PREFIX = "#", PROFILE_FROM_START = "GDHARNESS_PROFILE_FROM_START", ms = (seconds) => Math.round(seconds * 1e6) / 1000, RUNTIME_SCRIPT = "gdharness_runtime/runtime_autoload.gd", PROFILER_PROBE = "_profiler_probe", PROFILER_CALIBRATOR = "_calibrate_the_profiler", RUNTIME_ADDON2 = "res://addons/gdharness_runtime/", CALL_SITES = 40;
44444
44758
  var init_script_profile = __esm(() => {
44445
44759
  init_gdscript_source();
44446
44760
  init_godot_variant();
@@ -44471,18 +44785,18 @@ import {
44471
44785
  closeSync as closeSync3,
44472
44786
  constants,
44473
44787
  copyFileSync,
44474
- existsSync as existsSync23,
44788
+ existsSync as existsSync25,
44475
44789
  fstatSync,
44476
44790
  mkdirSync as mkdirSync17,
44477
44791
  openSync as openSync4,
44478
- readdirSync as readdirSync13,
44479
- readFileSync as readFileSync26,
44792
+ readdirSync as readdirSync14,
44793
+ readFileSync as readFileSync27,
44480
44794
  readSync as readSync2,
44481
44795
  rmdirSync as rmdirSync2,
44482
- statSync as statSync13,
44796
+ statSync as statSync14,
44483
44797
  writeFileSync as writeFileSync18
44484
44798
  } from "node:fs";
44485
- import { basename as basename9, dirname as dirname22, extname as extname4, isAbsolute as isAbsolute4, join as join30, normalize as normalize2, relative as relative6 } from "node:path";
44799
+ import { basename as basename10, dirname as dirname23, extname as extname4, isAbsolute as isAbsolute5, join as join32, normalize as normalize2, relative as relative6 } from "node:path";
44486
44800
  import { setTimeout as delay5 } from "node:timers/promises";
44487
44801
  import { fileURLToPath as fileURLToPath5 } from "node:url";
44488
44802
  function editorRestartTimeoutMs() {
@@ -44512,22 +44826,22 @@ function writtenFileRefusal(path, rule) {
44512
44826
  if (/^(res|user):\/\//.test(path)) {
44513
44827
  return `${named} ${path} is a Godot path; ${what} is saved outside the project, at an absolute path on this machine.`;
44514
44828
  }
44515
- if (!isAbsolute4(path)) {
44829
+ if (!isAbsolute5(path)) {
44516
44830
  return `${named} ${path} is not absolute; name the whole path, so where it is written does not depend on this server's directory.`;
44517
44831
  }
44518
44832
  if (!rule.extensions.includes(extname4(path).toLowerCase())) {
44519
44833
  return `${named} ${path} does not end in ${rule.extensions.join(" or ")}, which is what ${what} is.`;
44520
44834
  }
44521
- const directory = realPathOr(dirname22(path));
44522
- if (!existsSync23(directory)) {
44523
- return `${named}'s directory ${dirname22(path)} does not exist; ${what} creates no directories.`;
44835
+ const directory = realPathOr(dirname23(path));
44836
+ if (!existsSync25(directory)) {
44837
+ return `${named}'s directory ${dirname23(path)} does not exist; ${what} creates no directories.`;
44524
44838
  }
44525
- const where = join30(directory, basename9(path));
44839
+ const where = join32(directory, basename10(path));
44526
44840
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realPathOr(project), where));
44527
44841
  if (inside !== undefined) {
44528
44842
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
44529
44843
  }
44530
- const taken = rule.writes(where).find((file) => existsSync23(file));
44844
+ const taken = rule.writes(where).find((file) => existsSync25(file));
44531
44845
  if (taken !== undefined) {
44532
44846
  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.`;
44533
44847
  }
@@ -44636,7 +44950,7 @@ function exited(run) {
44636
44950
  return run.exitCode !== null || run.exitSignal !== null;
44637
44951
  }
44638
44952
  function howItExited(run) {
44639
- return run.exitSignal === null ? `exit code ${run.exitCode ?? "unknown"}` : `ended by ${run.exitSignal}, with no exit code`;
44953
+ return run.exitSignal === null ? exitCodeText(run.exitCode) : `ended by ${run.exitSignal}, with no exit code`;
44640
44954
  }
44641
44955
  function errorsUnread(run) {
44642
44956
  return run.throughEditor && typeof run.errorReport !== "string";
@@ -44744,7 +45058,7 @@ function endedWithoutACode(run) {
44744
45058
  function ourRunEnded(run, now) {
44745
45059
  const which = `${run.throughEditor ? "played in the editor" : "started"}${run.pid === null ? "" : ` (pid ${run.pid})`}`;
44746
45060
  const when = run.endedAt === undefined ? "" : ` ${Math.max(0, Math.round((now - run.endedAt) / 1000))}s ago`;
44747
- const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with code ${run.exitCode}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
45061
+ const how = run.endedThrough === "close" ? "this server asked it to close" : run.endedThrough === "keeper" ? "this server ended it through the keeper holding it" : run.endedThrough === "signal" ? "this server signalled it" : run.endedThrough === "editor" ? "this server asked the editor to stop it" : run.exitCode !== null ? `it exited by itself with ${exitCodeText(run.exitCode)}` : run.exitSignal === null ? "it is no longer running" : `it was ended by ${run.exitSignal}`;
44748
45062
  return `No game with the runtime addon is running: the game this server ${which} ended${when}, and ${how}. editor_output has what it printed, and editor_run start runs it again.`;
44749
45063
  }
44750
45064
  function stopVerdict(stop) {
@@ -44999,7 +45313,12 @@ function unlistedValues(schema, value, path) {
44999
45313
  const fields = schema;
45000
45314
  const listed = fields["enum"];
45001
45315
  if (Array.isArray(listed) && typeof value === "string") {
45002
- return listed.includes(value) ? [] : [`${path} as one of ${listed.join(", ")}, not ${JSON.stringify(value)}`];
45316
+ if (listed.includes(value)) {
45317
+ return [];
45318
+ }
45319
+ return [
45320
+ listed.length === 1 ? `${path} only as ${String(listed[0])}, not ${JSON.stringify(value)}` : `${path} as one of ${listed.join(", ")}, not ${JSON.stringify(value)}`
45321
+ ];
45003
45322
  }
45004
45323
  if (Array.isArray(value)) {
45005
45324
  return value.flatMap((element, index) => unlistedValues(fields["items"], element, `${path}[${index}]`));
@@ -45063,7 +45382,7 @@ function notRunNote(notRun, failFast) {
45063
45382
  function exportPresetRefusal(projectPath, preset) {
45064
45383
  let text;
45065
45384
  try {
45066
- text = readFileSync26(join30(projectPath, "export_presets.cfg"), "utf8");
45385
+ text = readFileSync27(join32(projectPath, "export_presets.cfg"), "utf8");
45067
45386
  } catch {
45068
45387
  return `This project has no export_presets.cfg, so it has no preset '${preset}' to export with. project_export add_preset writes one, as the editor's Project > Export does.`;
45069
45388
  }
@@ -45085,7 +45404,7 @@ function exportPresetRefusal(projectPath, preset) {
45085
45404
  }
45086
45405
  return names.length === 0 ? `export_presets.cfg names no preset, so there is no '${preset}' to export with. project_export add_preset adds one.` : `This project has no export preset named '${preset}'. Its presets are ${names.map((name) => `'${name}'`).join(", ")}.`;
45087
45406
  }
45088
- function exportAnswer(ending, asked, before, after, beside = []) {
45407
+ function exportAnswer(ending, asked, before, after, beside = [], docCacheNote = null) {
45089
45408
  const { log } = ending;
45090
45409
  const written = after !== null && after !== before;
45091
45410
  const notCopied = librariesNotCopied(log.all);
@@ -45102,6 +45421,7 @@ function exportAnswer(ending, asked, before, after, beside = []) {
45102
45421
  librariesNotLoaded: notCopied,
45103
45422
  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.`
45104
45423
  },
45424
+ ...docCacheNote === null ? {} : { docCacheNote },
45105
45425
  ...foldedEntriesAnswer(log)
45106
45426
  };
45107
45427
  if (verdict.exported) {
@@ -45126,9 +45446,9 @@ function writtenBeside(before, after, output, within) {
45126
45446
  function filesIn(folder) {
45127
45447
  const found = new Map;
45128
45448
  try {
45129
- for (const entry of readdirSync13(folder, { withFileTypes: true })) {
45449
+ for (const entry of readdirSync14(folder, { withFileTypes: true })) {
45130
45450
  if (entry.isFile()) {
45131
- found.set(entry.name, statSync13(join30(folder, entry.name)).mtimeMs);
45451
+ found.set(entry.name, statSync14(join32(folder, entry.name)).mtimeMs);
45132
45452
  }
45133
45453
  }
45134
45454
  } catch {}
@@ -45158,7 +45478,7 @@ function unsavedInEditorNote(scripts) {
45158
45478
  return `${named} ${one ? "is" : "are"} open in the editor's script ${one ? "tab" : "tabs"} with unsaved edits, and saving ${one ? "that tab" : "them"} there writes ${one ? "its" : "their"} text over what was written here: close ${one ? "it" : "them"} without saving, or save first and edit again.`;
45159
45479
  }
45160
45480
  function editorAddonMissing(projectPath) {
45161
- if (!existsSync23(join30(projectPath, "project.godot"))) {
45481
+ if (!existsSync25(join32(projectPath, "project.godot"))) {
45162
45482
  return null;
45163
45483
  }
45164
45484
  const report = inspectProject(projectPath);
@@ -45324,8 +45644,34 @@ function scriptErrorsNote(errors, {
45324
45644
  const opening = unnamed === 0 ? `No tests ran: gdUnit4 could not load ${scripts === 1 ? "one script" : `${scripts} scripts`} while looking for suites, and it runs no suite at all when one fails to load.` : `No tests ran: ${scripts + unnamed} scripts did not compile, and gdUnit4 runs no suite at all when one fails to load. It could not load ${scripts === 1 ? "one" : scripts} of them while looking for suites, and ${unnamed === 1 ? "the one it did not name comes" : `the ${unnamed} it did not name come`} first.`;
45325
45645
  return [opening, named, following, rest, after].filter((part) => part !== "").join(" ");
45326
45646
  }
45327
- function timedOutVerdict(timeoutMs, silentForMs) {
45647
+ function autoloadNotCompiledNote(at, errors, stoppedEarly) {
45648
+ const first = errors.scriptErrors[0];
45649
+ const where = first === undefined ? "" : `, and the first error the engine raised compiling was at ${first.path}${first.line === null ? "" : `:${first.line}`}: ${first.message}`;
45650
+ const others = errors.scriptErrors.length - 1;
45651
+ const more = others > 0 ? ` ${others === 1 ? "One more error is" : `${others} more errors are`} under scriptErrors.` : "";
45652
+ const through = errors.failedThroughDependency.length;
45653
+ const dependents = through > 0 ? ` ${through === 1 ? "One script" : `${through} scripts`} failed only through ${first === undefined ? "it" : "that"}, listed under failedThroughDependency.` : "";
45654
+ const ended = stoppedEarly ? "The run was stopped as soon as the engine said so rather than left to its timeout" : "The run ended before that was read, and what its suites said is set aside";
45655
+ const stop = /[.!?]\)?$/.test(where) ? "" : ".";
45656
+ return `No result counts: the autoload ${at.name} (${at.path}) did not compile${where}${stop}${more}${dependents} Every suite runs with the autoloads, so a case calling into a script that did not compile fails the same way on every pass, and one waiting in a loop for the call to succeed never ends. ${ended}. script_diagnostics checks a script without running anything.`;
45657
+ }
45658
+ function repeatingScriptError(entries, byScript) {
45659
+ const last = entries.at(-1);
45660
+ if (last === undefined || !byScript(last)) {
45661
+ return null;
45662
+ }
45663
+ const where = last.detail[0] ?? null;
45664
+ const times = entries.filter((entry) => entry.text === last.text && (entry.detail[0] ?? null) === where && byScript(entry)).length;
45665
+ return times >= LOOPING_AFTER ? { text: last.text, where: where?.replace(/^at: /, "") ?? null, times } : null;
45666
+ }
45667
+ function timedOutVerdict(timeoutMs, silentForMs, repeating = null) {
45328
45668
  const quiet = Math.round(silentForMs);
45669
+ if (repeating !== null) {
45670
+ return {
45671
+ verdict: `timed out after ${timeoutMs} ms repeating one script error, printed ${repeating.times} times: ${repeating.text}${repeating.where === null ? "" : ` (${repeating.where})`}. A line that fails on every pass of a loop does this, and a longer timeoutMs would not end it`,
45672
+ hung: false
45673
+ };
45674
+ }
45329
45675
  return quiet >= Math.min(STALLED_AFTER_MS, timeoutMs / 2) ? {
45330
45676
  verdict: `hung: killed after ${timeoutMs} ms, having printed nothing for the last ${quiet} ms`,
45331
45677
  hung: true
@@ -45396,7 +45742,7 @@ function profileFrameFunctions(projectPath) {
45396
45742
  let projectGodot = null;
45397
45743
  if (projectPath !== null) {
45398
45744
  try {
45399
- projectGodot = readFileSync26(join30(projectPath, "project.godot"), "utf8");
45745
+ projectGodot = readFileSync27(join32(projectPath, "project.godot"), "utf8");
45400
45746
  } catch {}
45401
45747
  }
45402
45748
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -45420,7 +45766,7 @@ function projectSourceOf(projectPath) {
45420
45766
  let source = null;
45421
45767
  if (inside.ok) {
45422
45768
  try {
45423
- source = readFileSync26(inside.absolutePath, "utf8");
45769
+ source = readFileSync27(inside.absolutePath, "utf8");
45424
45770
  } catch {}
45425
45771
  }
45426
45772
  read.set(script, source);
@@ -45471,6 +45817,9 @@ function profileAnswer(reading, limit) {
45471
45817
  if (reading.coveredMs > 0 && table.scriptMs > reading.coveredMs) {
45472
45818
  notes.push(`scriptMs, ${table.scriptMs}ms, is more than the ${reading.coveredMs}ms the profile covers.${table.superCallers > 0 ? " The super calls above, counted twice, can account for some or all of that; whatever they do not is" : " One thread cannot spend more time than passes, so that is"} functions running on several threads at once, through WorkerThreadPool or Thread. The engine adds every thread's time to the same function and does not say which thread a call ran on, so a function's selfMs is summed over every thread it ran on rather than taken out of frames, and a function that waits for the others, through wait_for_group_task_completion or wait_to_finish, has the wait in its own selfMs as well.`);
45473
45819
  }
45820
+ if (table.addonFunctions > 0) {
45821
+ notes.push(`The gdharness runtime addon's own functions took ${table.addonMs}ms of self time over ${table.addonFunctions === 1 ? "one function" : `${table.addonFunctions} functions`}, answering tools such as runtime_invoke and sending the profile. They are left out of functions, functionsCounted and scriptMs and summed under runtimeAddonMs, since they are not the game's work, though that time was spent in the game's frames.`);
45822
+ }
45474
45823
  notes.push(...cappedNotes(totals));
45475
45824
  if (totals.unreadable > 0) {
45476
45825
  notes.push(`${totals.unreadable} message${totals.unreadable === 1 ? "" : "s"} from the game could not be read, so the totals have that many gaps.`);
@@ -45485,6 +45834,8 @@ function profileAnswer(reading, limit) {
45485
45834
  scriptMs: table.scriptMs,
45486
45835
  perCallOverheadUs: table.perCallUs ?? undefined,
45487
45836
  functionsCounted: table.counted,
45837
+ runtimeAddonMs: table.addonFunctions > 0 ? table.addonMs : undefined,
45838
+ runtimeAddonFunctions: table.addonFunctions > 0 ? table.addonFunctions : undefined,
45488
45839
  omitted: table.omitted > 0 ? table.omitted : undefined,
45489
45840
  functions: table.functions,
45490
45841
  callers: reading.callersOf === undefined ? undefined : callersAnswer(totals, reading.callersOf),
@@ -45570,7 +45921,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
45570
45921
  }
45571
45922
  function gdUnitFailsOnScriptErrors(projectPath) {
45572
45923
  try {
45573
- return settingKeys(readFileSync26(join30(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
45924
+ return settingKeys(readFileSync27(join32(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
45574
45925
  } catch {
45575
45926
  return true;
45576
45927
  }
@@ -45585,13 +45936,13 @@ function uncaughtNote(uncaught) {
45585
45936
  }
45586
45937
  function elsewhereIn(projectPath, asked) {
45587
45938
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
45588
- if (wanted === "" || existsSync23(join30(projectPath, wanted))) {
45939
+ if (wanted === "" || existsSync25(join32(projectPath, wanted))) {
45589
45940
  return "";
45590
45941
  }
45591
45942
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
45592
45943
  let near = [];
45593
45944
  try {
45594
- near = readdirSync13(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()));
45945
+ near = readdirSync14(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()));
45595
45946
  } catch {
45596
45947
  return "";
45597
45948
  }
@@ -45682,23 +46033,23 @@ function engineChecked(why) {
45682
46033
  }
45683
46034
  function isDirectory(path) {
45684
46035
  try {
45685
- return statSync13(path).isDirectory();
46036
+ return statSync14(path).isDirectory();
45686
46037
  } catch {
45687
46038
  return false;
45688
46039
  }
45689
46040
  }
45690
46041
  function scriptsUnder(directory, prefix) {
45691
- if (existsSync23(join30(directory, ".gdignore"))) {
46042
+ if (existsSync25(join32(directory, ".gdignore"))) {
45692
46043
  return [];
45693
46044
  }
45694
46045
  const found = [];
45695
- for (const entry of readdirSync13(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
46046
+ for (const entry of readdirSync14(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
45696
46047
  if (entry.name.startsWith(".")) {
45697
46048
  continue;
45698
46049
  }
45699
46050
  const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
45700
46051
  if (entry.isDirectory()) {
45701
- found.push(...scriptsUnder(join30(directory, entry.name), path));
46052
+ found.push(...scriptsUnder(join32(directory, entry.name), path));
45702
46053
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
45703
46054
  found.push(path);
45704
46055
  }
@@ -45706,7 +46057,7 @@ function scriptsUnder(directory, prefix) {
45706
46057
  return found;
45707
46058
  }
45708
46059
  function hasMainScene(projectFile) {
45709
- const scene = parseProjectGodot(readFileSync26(projectFile, "utf8"))["application"]?.["run/main_scene"];
46060
+ const scene = parseProjectGodot(readFileSync27(projectFile, "utf8"))["application"]?.["run/main_scene"];
45710
46061
  return typeof scene === "string" && scene !== "";
45711
46062
  }
45712
46063
  function runtimeAddonAnnouncesEditors(projectPath) {
@@ -45714,13 +46065,13 @@ function runtimeAddonAnnouncesEditors(projectPath) {
45714
46065
  return false;
45715
46066
  }
45716
46067
  try {
45717
- return readFileSync26(join30(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
46068
+ return readFileSync27(join32(projectPath, RUNTIME_AUTOLOAD.path), "utf8").includes('"editor_pid"');
45718
46069
  } catch {
45719
46070
  return false;
45720
46071
  }
45721
46072
  }
45722
46073
  function editorPlaysHeadless(projectFile) {
45723
- const runArgs = parseProjectGodot(readFileSync26(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
46074
+ const runArgs = parseProjectGodot(readFileSync27(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
45724
46075
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
45725
46076
  }
45726
46077
  function camelCased(params) {
@@ -45739,7 +46090,7 @@ function needsItsOwnProcess(asked) {
45739
46090
  class GodotServer {
45740
46091
  stdio = null;
45741
46092
  locator = new GodotLocator;
45742
- operationsScript = join30(__dirname2, "godot", "operations", "godot_operations.gd");
46093
+ operationsScript = join32(__dirname2, "godot", "operations", "godot_operations.gd");
45743
46094
  godotBridge;
45744
46095
  tools = buildToolDefinitions();
45745
46096
  activeProcess = null;
@@ -46000,14 +46351,14 @@ class GodotServer {
46000
46351
  }
46001
46352
  noteTheStart() {
46002
46353
  const cwd = process.cwd();
46003
- const mcpJson = join30(cwd, ".mcp.json");
46354
+ const mcpJson = join32(cwd, ".mcp.json");
46004
46355
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
46005
46356
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
46006
46357
  writeLifecycle("started", {
46007
46358
  argv: process.argv.slice(1),
46008
46359
  cwd,
46009
46360
  project: this.ownProject,
46010
- mcpJson: existsSync23(mcpJson) ? mcpJson : null
46361
+ mcpJson: existsSync25(mcpJson) ? mcpJson : null
46011
46362
  });
46012
46363
  if (predecessor !== null) {
46013
46364
  writeLifecycle("replaced", { project: this.ownProject, predecessor });
@@ -46148,7 +46499,7 @@ class GodotServer {
46148
46499
  return checked.response;
46149
46500
  }
46150
46501
  const named = args["projectPath"];
46151
- if (typeof named === "string" && existsSync23(join30(named, "project.godot"))) {
46502
+ if (typeof named === "string" && existsSync25(join32(named, "project.godot"))) {
46152
46503
  this.lastProjectPath = named;
46153
46504
  }
46154
46505
  this.updates.refresh();
@@ -46184,7 +46535,7 @@ class GodotServer {
46184
46535
  let notSaved;
46185
46536
  try {
46186
46537
  savedAt = answer.content.map((item, index) => {
46187
- const file = join30(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
46538
+ const file = join32(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
46188
46539
  writeFileSync18(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
46189
46540
  return file;
46190
46541
  });
@@ -46802,8 +47153,8 @@ class GodotServer {
46802
47153
  if (path === undefined) {
46803
47154
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
46804
47155
  }
46805
- const file = join30(path, "project.godot");
46806
- if (!existsSync23(file)) {
47156
+ const file = join32(path, "project.godot");
47157
+ if (!existsSync25(file)) {
46807
47158
  return {
46808
47159
  ok: false,
46809
47160
  response: this.createErrorResponse(`Not a Godot project: ${path}`, [
@@ -46849,7 +47200,7 @@ class GodotServer {
46849
47200
  }
46850
47201
  runEnvironment(args) {
46851
47202
  const savesIn = readNonEmptyString(args, "savesIn");
46852
- if (savesIn !== undefined && !isAbsolute4(savesIn)) {
47203
+ if (savesIn !== undefined && !isAbsolute5(savesIn)) {
46853
47204
  return {
46854
47205
  ok: false,
46855
47206
  response: this.createErrorResponse(`savesIn must be an absolute path, not "${savesIn}".`, [
@@ -47108,7 +47459,8 @@ class GodotServer {
47108
47459
  uidsCreated: given,
47109
47460
  stillWithoutUid: after,
47110
47461
  note: uidsLeftNote(after.length),
47111
- ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote }
47462
+ ...imported.extensionNote === undefined ? {} : { extensionNote: imported.extensionNote },
47463
+ ...imported.docCacheNote === undefined ? {} : { docCacheNote: imported.docCacheNote }
47112
47464
  }
47113
47465
  }, scanned));
47114
47466
  }
@@ -47347,6 +47699,9 @@ class GodotServer {
47347
47699
  if (imported.extensionNote !== undefined) {
47348
47700
  extra["extensionNote"] = imported.extensionNote;
47349
47701
  }
47702
+ if (imported.docCacheNote !== undefined) {
47703
+ extra["docCacheNote"] = imported.docCacheNote;
47704
+ }
47350
47705
  }
47351
47706
  const after = await this.operation("get_import_status", statusAsk, projectPath);
47352
47707
  if (!after.ok) {
@@ -47459,11 +47814,11 @@ class GodotServer {
47459
47814
  if (!engine.ok) {
47460
47815
  return engine.response;
47461
47816
  }
47462
- const application = parseProjectGodot(readFileSync26(project.value.file, "utf8"))["application"] ?? {};
47817
+ const application = parseProjectGodot(readFileSync27(project.value.file, "utf8"))["application"] ?? {};
47463
47818
  const name = application["config/name"];
47464
47819
  const mainScene = application["run/main_scene"];
47465
47820
  const info = {
47466
- name: typeof name === "string" && name !== "" ? name : basename9(project.value.path),
47821
+ name: typeof name === "string" && name !== "" ? name : basename10(project.value.path),
47467
47822
  path: project.value.path,
47468
47823
  mainScene: typeof mainScene === "string" && mainScene !== "" ? mainScene : null,
47469
47824
  godotVersion: await this.godotVersion(engine.value),
@@ -47546,18 +47901,18 @@ class GodotServer {
47546
47901
  if (!engine.ok) {
47547
47902
  return engine.response;
47548
47903
  }
47549
- const folder = dirname22(output.absolutePath);
47904
+ const folder = dirname23(output.absolutePath);
47550
47905
  try {
47551
47906
  mkdirSync17(folder, { recursive: true });
47552
47907
  } catch (error) {
47553
- return this.createErrorResponse(`Cannot create ${dirname22(output.relativePath)} to export into: ${errorMessage(error)}`);
47908
+ return this.createErrorResponse(`Cannot create ${dirname23(output.relativePath)} to export into: ${errorMessage(error)}`);
47554
47909
  }
47555
47910
  const debug = readBoolean(args, "debug") ?? false;
47556
47911
  const exportLogs = scratchDirectory("export");
47557
47912
  const exportArgs = [
47558
47913
  "--headless",
47559
47914
  "--log-file",
47560
- join30(exportLogs, "engine.log"),
47915
+ join32(exportLogs, "engine.log"),
47561
47916
  "--path",
47562
47917
  project.value.path,
47563
47918
  debug ? "--export-debug" : "--export-release",
@@ -47565,24 +47920,37 @@ class GodotServer {
47565
47920
  output.absolutePath
47566
47921
  ];
47567
47922
  this.logDebug(`Exporting: ${engine.value} ${exportArgs.join(" ")}`);
47568
- const writtenAt = () => existsSync23(output.absolutePath) ? statSync13(output.absolutePath).mtimeMs : null;
47569
- const before = writtenAt();
47570
- const listedBefore = filesIn(folder);
47571
- const freed = freeLibraryCopies(project.value.path);
47572
- if (freed.setAside.length > 0 || freed.swept.length > 0) {
47573
- this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
47574
- }
47575
- let ending;
47923
+ const writtenAt = () => existsSync25(output.absolutePath) ? statSync14(output.absolutePath).mtimeMs : null;
47924
+ const caches = editorCacheDirectory(engine.value);
47925
+ const exportOnce = async () => {
47926
+ const before = writtenAt();
47927
+ const listedBefore = filesIn(folder);
47928
+ const cachesBefore = docCachesWritten(caches);
47929
+ const freed = freeLibraryCopies(project.value.path);
47930
+ if (freed.setAside.length > 0 || freed.swept.length > 0) {
47931
+ this.logDebug(`Before the export: set aside ${freed.setAside.join(", ")}; swept ${freed.swept.join(", ")}`);
47932
+ }
47933
+ const ending = await runEngine(engine.value, exportArgs, {
47934
+ timeout: { ms: 300000, said: "five minutes" }
47935
+ });
47936
+ const crash = docCacheCrash(ending, cachesBefore, docCachesWritten(caches), "export");
47937
+ return { ending, before, listedBefore, crash };
47938
+ };
47939
+ let run;
47940
+ let docCacheNote;
47576
47941
  try {
47577
- ending = await runEngine(engine.value, exportArgs, { timeout: { ms: 300000, said: "five minutes" } });
47942
+ const first = await exportOnce();
47943
+ docCacheNote = first.crash;
47944
+ run = first.crash === null ? first : await exportOnce();
47578
47945
  } finally {
47579
47946
  discard(exportLogs);
47580
47947
  }
47948
+ const { ending, before, listedBefore } = run;
47581
47949
  if (ending.exitSignal === null && ending.exitCode === null) {
47582
47950
  return this.createErrorResponse(`Export could not be run: ${ending.failure ?? "the engine gave no exit status"}`);
47583
47951
  }
47584
- const beside = writtenBeside(listedBefore, filesIn(folder), basename9(output.absolutePath), dirname22(output.relativePath).replaceAll("\\", "/"));
47585
- return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside);
47952
+ const beside = writtenBeside(listedBefore, filesIn(folder), basename10(output.absolutePath), dirname23(output.relativePath).replaceAll("\\", "/"));
47953
+ return exportAnswer(ending, { preset, outputPath: output.relativePath, debug }, before, writtenAt(), beside, docCacheNote);
47586
47954
  }
47587
47955
  reportTestProgress(log) {
47588
47956
  const progress = callProgress();
@@ -47635,10 +48003,10 @@ class GodotServer {
47635
48003
  if (movie !== null) {
47636
48004
  return this.createErrorResponse(movie);
47637
48005
  }
47638
- const missing = paths.value.filter((path) => !existsSync23(join30(project.value.path, path.slice("res://".length))));
48006
+ const missing = paths.value.filter((path) => !existsSync25(join32(project.value.path, path.slice("res://".length))));
47639
48007
  const asked = paths.value.filter((path) => !missing.includes(path));
47640
48008
  const runner = "addons/gdUnit4/bin/GdUnitCmdTool.gd";
47641
- const noRunner = !existsSync23(join30(project.value.path, runner));
48009
+ const noRunner = !existsSync25(join32(project.value.path, runner));
47642
48010
  if (asked.length === 0) {
47643
48011
  const verdict = `nothing at ${missing.join(" and ")}`;
47644
48012
  const andNoRunner = noRunner ? `, and gdUnit4 is not installed in this project: no ${runner}` : "";
@@ -47708,42 +48076,57 @@ class GodotServer {
47708
48076
  lastSaid = Date.now();
47709
48077
  });
47710
48078
  const reporting = this.reportTestProgress(run.log);
48079
+ const autoloaded = scriptsAutoloaded(project.value.path);
47711
48080
  let silentForMs = 0;
48081
+ const stopped = { for: null };
47712
48082
  const timedOut = await new Promise((resolve) => {
48083
+ let read = 0;
48084
+ const watching = setInterval(() => {
48085
+ const entries = run.log.all;
48086
+ for (const entry of entries.slice(read)) {
48087
+ stopped.for ??= autoloadNotCompiled(entry, autoloaded);
48088
+ }
48089
+ read = entries.length;
48090
+ if (stopped.for !== null) {
48091
+ clearInterval(watching);
48092
+ run.process.kill();
48093
+ }
48094
+ }, 100);
47713
48095
  const timer = setTimeout(() => {
48096
+ clearInterval(watching);
47714
48097
  silentForMs = Date.now() - lastSaid;
47715
48098
  run.process.kill();
47716
48099
  resolve(true);
47717
48100
  }, timeoutMs);
47718
- run.process.once("exit", () => {
48101
+ const ended = () => {
48102
+ clearInterval(watching);
47719
48103
  clearTimeout(timer);
47720
48104
  resolve(false);
47721
- });
47722
- run.process.once("error", () => {
47723
- clearTimeout(timer);
47724
- resolve(false);
47725
- });
48105
+ };
48106
+ run.process.once("exit", ended);
48107
+ run.process.once("error", ended);
47726
48108
  });
47727
48109
  clearInterval(reporting);
47728
- if (timedOut && run.process.pid !== undefined) {
48110
+ if ((timedOut || stopped.for !== null) && run.process.pid !== undefined) {
47729
48111
  await untilGone([run.process.pid]);
47730
48112
  }
47731
48113
  await run.settled;
47732
- const reportsDir = join30(project.value.path, ".godot", ...ours.split("/"));
48114
+ const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
48115
+ const reportsDir = join32(project.value.path, ".godot", ...ours.split("/"));
47733
48116
  let report = null;
47734
48117
  let reportProblem = null;
47735
48118
  try {
47736
- const written = existsSync23(reportsDir) ? readdirSync13(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
48119
+ const written = existsSync25(reportsDir) && notCompiled === null ? readdirSync14(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
47737
48120
  const newest = written.at(-1);
47738
48121
  if (newest !== undefined) {
47739
- report = parseJUnit(readFileSync26(join30(reportsDir, newest, "results.xml"), "utf8"));
48122
+ report = parseJUnit(readFileSync27(join32(reportsDir, newest, "results.xml"), "utf8"));
47740
48123
  }
47741
48124
  } catch (error) {
47742
48125
  reportProblem = errorMessage(error);
47743
48126
  } finally {
47744
48127
  discard(reportsDir, userData);
47745
48128
  try {
47746
- rmdirSync2(dirname22(reportsDir));
48129
+ rmdirSync2(dirname23(reportsDir));
47747
48130
  } catch {}
47748
48131
  }
47749
48132
  const engineEntries = foldedRepeats(run.log.select({
@@ -47768,14 +48151,14 @@ class GodotServer {
47768
48151
  limit: Number.POSITIVE_INFINITY
47769
48152
  }).entries;
47770
48153
  const said = printed.map((entry) => entry.text);
47771
- const nothingRan = timedOut ? null : whyNoReport(said, asked.join(" and "));
47772
- const cutShort = timedOut ? timedOutVerdict(timeoutMs, silentForMs) : null;
48154
+ const nothingRan = timedOut || notCompiled !== null ? null : whyNoReport(said, asked.join(" and "));
48155
+ const cutShort = timedOut && notCompiled === null ? timedOutVerdict(timeoutMs, silentForMs, repeatingScriptError(run.log.all, (entry) => run.log.raisedByScript(entry))) : null;
47773
48156
  const hung = cutShort?.hung ?? false;
47774
- const printedErrors = timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
48157
+ const printedErrors = notCompiled !== null ? compileErrorsIn(run.log.all) : timedOut || exitCode !== 105 ? [] : scriptErrorsPrinted(said);
47775
48158
  const declaring = printedErrors.some((error) => unresolvedClassIn(error.message) !== null) ? await originsOf(printedErrors, declaredClasses(project.value.path), checkWithEngine(engine.value, project.value.path)) : { origins: [], unconfirmed: [] };
47776
48159
  const scriptErrors = [...declaring.origins, ...printedErrors];
47777
48160
  const brokenScripts = new Set(scriptErrors.map((error) => error.path)).size;
47778
- const ranAs = cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? (run.exitSignal === null ? `exit ${exitCode ?? "unknown"}` : howItExited(run));
48161
+ const ranAs = notCompiled !== null ? `the autoload ${notCompiled.name} (${notCompiled.path}) did not compile, so no result counts` : cutShort !== null ? cutShort.verdict : nothingRan ?? (brokenScripts > 0 ? `script errors in ${brokenScripts === 1 ? "one script" : `${brokenScripts} scripts`}, so no suite ran` : undefined) ?? (uncaught.length > 0 ? `${uncaught.length === 1 ? "one script error" : `${uncaught.length} script errors`} that failed no case` : undefined) ?? (exitCode === null ? undefined : verdicts[exitCode]) ?? howItExited(run);
47779
48162
  const nothingAt = missing.join(" and ");
47780
48163
  const verdict = missing.length === 0 ? ranAs : `${ranAs}; nothing at ${nothingAt}`;
47781
48164
  const missingNote = missing.length === 0 ? undefined : `Nothing at ${nothingAt}, so ${missing.length === 1 ? "it was" : "they were"} left out and only ${asked.join(", ")} ran; a run of fewer paths than were asked for is not called a pass.${elsewhereIn(project.value.path, missing[0] ?? "")}`;
@@ -47803,7 +48186,7 @@ class GodotServer {
47803
48186
  }),
47804
48187
  ...running === null ? {} : { suiteRunning: running }
47805
48188
  };
47806
- const said = nothingRan !== null ? `No tests ran: ${ranAs}.` : scriptErrors.length > 0 ? scriptErrorsNote(printedErrors, declaring) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${ranAs}.` : `The test run wrote no report (${ranAs}).${finishedBeforeTheEnd(finished.length, failing.length, running)}`;
48189
+ const said = notCompiled !== null ? autoloadNotCompiledNote(notCompiled, foldedScriptErrors(scriptErrors), stopped.for !== null) : nothingRan !== null ? `No tests ran: ${ranAs}.` : scriptErrors.length > 0 ? scriptErrorsNote(printedErrors, declaring) : reportProblem !== null ? `The test run's report could not be read (${reportProblem}); the engine's own verdict was ${ranAs}.` : `The test run wrote no report (${ranAs}).${finishedBeforeTheEnd(finished.length, failing.length, running)}`;
47807
48190
  const note = missingNote === undefined ? said : `${said} ${missingNote}`;
47808
48191
  if (callSignal()?.aborted === true) {
47809
48192
  keepCutTestRun(project.value.path, { cutAt: new Date().toISOString(), paths: asked, ...atTheEnd });
@@ -48102,11 +48485,11 @@ class GodotServer {
48102
48485
  const cached = cachedClasses(projectPath);
48103
48486
  const sourceOf = (resourcePath) => {
48104
48487
  const contained = resolveWithinProject(projectPath, resourcePath);
48105
- if (!contained.ok || !existsSync23(contained.absolutePath)) {
48488
+ if (!contained.ok || !existsSync25(contained.absolutePath)) {
48106
48489
  return null;
48107
48490
  }
48108
48491
  try {
48109
- return readFileSync26(contained.absolutePath, "utf8");
48492
+ return readFileSync27(contained.absolutePath, "utf8");
48110
48493
  } catch {
48111
48494
  return null;
48112
48495
  }
@@ -48196,11 +48579,11 @@ class GodotServer {
48196
48579
  }
48197
48580
  sourceOfClass(projectPath, resourcePath) {
48198
48581
  const contained = resolveWithinProject(projectPath, resourcePath);
48199
- if (!contained.ok || !existsSync23(contained.absolutePath)) {
48582
+ if (!contained.ok || !existsSync25(contained.absolutePath)) {
48200
48583
  return null;
48201
48584
  }
48202
48585
  try {
48203
- return readFileSync26(contained.absolutePath, "utf8");
48586
+ return readFileSync27(contained.absolutePath, "utf8");
48204
48587
  } catch {
48205
48588
  return null;
48206
48589
  }
@@ -48358,7 +48741,7 @@ class GodotServer {
48358
48741
  ...Array.isArray(stack) ? {
48359
48742
  stack: stack.map((frame) => ({
48360
48743
  ...frame,
48361
- ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join30(projectPath, frame["script"].slice("res://".length)) } : {}
48744
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join32(projectPath, frame["script"].slice("res://".length)) } : {}
48362
48745
  }))
48363
48746
  } : {}
48364
48747
  });
@@ -48637,7 +49020,7 @@ class GodotServer {
48637
49020
  return null;
48638
49021
  }
48639
49022
  try {
48640
- return settingKeys(readFileSync26(join30(projectPath, "project.godot"), "utf8"));
49023
+ return settingKeys(readFileSync27(join32(projectPath, "project.godot"), "utf8"));
48641
49024
  } catch {
48642
49025
  return null;
48643
49026
  }
@@ -49074,7 +49457,7 @@ class GodotServer {
49074
49457
  }
49075
49458
  async runtimeUp(projectPath, before, budget, withArgs) {
49076
49459
  const budgetMs = budget.ms;
49077
- if (!existsSync23(join30(projectPath, RUNTIME_AUTOLOAD.path))) {
49460
+ if (!existsSync25(join32(projectPath, RUNTIME_AUTOLOAD.path))) {
49078
49461
  return runtimeVerdict(null, {
49079
49462
  addon: false,
49080
49463
  budgetMs,
@@ -49160,7 +49543,7 @@ class GodotServer {
49160
49543
  }
49161
49544
  let announcedAt;
49162
49545
  try {
49163
- announcedAt = statSync13(endpoint.file).mtimeMs;
49546
+ announcedAt = statSync14(endpoint.file).mtimeMs;
49164
49547
  } catch {
49165
49548
  return;
49166
49549
  }
@@ -49354,7 +49737,7 @@ class GodotServer {
49354
49737
  return 0;
49355
49738
  }
49356
49739
  try {
49357
- return reportAlreadyInTranscript(readFileSync26(game.transcript), readFileSync26(report));
49740
+ return reportAlreadyInTranscript(readFileSync27(game.transcript), readFileSync27(report));
49358
49741
  } catch (error) {
49359
49742
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
49360
49743
  return 0;
@@ -50050,9 +50433,13 @@ class GodotServer {
50050
50433
  if (callersName !== undefined && !callersName.replace(/@([^.@]+)_(?:getter|setter)$/, "$1").split(".").every(isIdentifier)) {
50051
50434
  return this.createErrorResponse(`callersOf takes a function's name, or Class.name for the functions of one class, and ${JSON.stringify(callersName)} is neither.`, ["editor_run profile names each function as it answers; pass one of those"]);
50052
50435
  }
50436
+ const asked = {
50437
+ mark: readBoolean(args, "mark") === true,
50438
+ since: readString(args, "since") === "mark"
50439
+ };
50053
50440
  const going = await this.runStillGoing(run);
50054
50441
  const projectPath = run.projectPath;
50055
- const projectGone = projectPath !== null && !existsSync23(join30(projectPath, "project.godot"));
50442
+ const projectGone = projectPath !== null && !existsSync25(join32(projectPath, "project.godot"));
50056
50443
  if (projectGone && callersName !== undefined) {
50057
50444
  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"]);
50058
50445
  }
@@ -50062,7 +50449,7 @@ class GodotServer {
50062
50449
  ...callersName === undefined ? {} : { callersOf: { name: callersName, scripts: () => projectScripts(projectPath) } }
50063
50450
  };
50064
50451
  if (run.throughEditor) {
50065
- return await this.profileOfThePlayedGame(going, projectPath, fromProject, limit);
50452
+ return await this.profileOfThePlayedGame(run, going, fromProject, limit, asked);
50066
50453
  }
50067
50454
  const path = run.profile;
50068
50455
  if (path === undefined) {
@@ -50075,15 +50462,16 @@ class GodotServer {
50075
50462
  ]);
50076
50463
  }
50077
50464
  const totals = file.connected ? file : null;
50078
- return this.jsonTextResponse(profileAnswer({
50465
+ return this.profileReply(run, {
50079
50466
  through: "gdharness",
50080
50467
  running: going,
50081
50468
  totals,
50082
50469
  coveredMs: totals?.lastFrameAt == null ? 0 : totals.lastFrameAt - totals.startedAt,
50083
50470
  ...fromProject
50084
- }, limit));
50471
+ }, limit, asked);
50085
50472
  }
50086
- async profileOfThePlayedGame(going, projectPath, fromProject, limit) {
50473
+ async profileOfThePlayedGame(run, going, fromProject, limit, asked) {
50474
+ const { projectPath } = run;
50087
50475
  const read = await this.handleViaBridge("profile_read", {});
50088
50476
  if (read.isError === true) {
50089
50477
  return read;
@@ -50105,12 +50493,13 @@ class GodotServer {
50105
50493
  return this.createErrorResponse("The editor says it is playing and has no debugger session for the game, so there is nothing to switch the profiler on in yet.", ["Ask again in a moment, once the game is up"]);
50106
50494
  }
50107
50495
  const on = await this.firstProfiledFrame();
50496
+ const unmarked = asked.mark || asked.since ? " Nothing was marked, and nothing is counted from a mark: the profile begins now, so an answer without since covers the same span." : "";
50108
50497
  return this.jsonTextResponse({
50109
50498
  through: "editor",
50110
50499
  running: true,
50111
50500
  started: true,
50112
50501
  functions: [],
50113
- note: on ? "The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start." : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.`
50502
+ note: on ? `The profiler was switched on now, for the game the editor is playing, and its first frame has arrived, so it covers what the game does from here on. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}` : `The profiler was switched on for the game the editor is playing, and no frame of it had arrived ${PROFILER_ON_MS}ms later: what the game does before the first one is not counted. Ask again for the functions; editor_run start with profile: true covers a game from its start.${unmarked}`
50114
50503
  });
50115
50504
  }
50116
50505
  const functions = asParams(held["functions"]);
@@ -50133,13 +50522,70 @@ class GodotServer {
50133
50522
  ];
50134
50523
  }))
50135
50524
  } : null;
50136
- return this.jsonTextResponse(profileAnswer({
50525
+ return this.profileReply(run, {
50137
50526
  through: "editor",
50138
50527
  running: going,
50139
50528
  totals,
50140
50529
  coveredMs: readNonNegativeNumber(held, "coveredMs") ?? 0,
50141
50530
  ...fromProject
50142
- }, limit));
50531
+ }, limit, asked);
50532
+ }
50533
+ profileReply(run, reading, limit, asked) {
50534
+ const markPath = profileMarkPath(run.startedAt);
50535
+ const now = Date.now();
50536
+ let shown = reading;
50537
+ const lead = [];
50538
+ let sinceFields = {};
50539
+ if (asked.since) {
50540
+ const mark = readProfileMark(markPath);
50541
+ if (mark === null || mark.runStartedAt !== run.startedAt || mark.projectPath !== run.projectPath) {
50542
+ return this.createErrorResponse("No mark has been set in this run, so there is nothing to count from.", ["editor_run profile with mark: true sets one at the totals it answers with"]);
50543
+ }
50544
+ const added = reading.totals === null ? null : totalsSince(reading.totals, mark.totals);
50545
+ if (added === null) {
50546
+ return this.createErrorResponse("The profile holds less than it did at the mark, so the profiler was started again since, and what it holds now cannot be counted from the mark.", ["editor_run profile with mark: true sets a mark in the profile as it is now"]);
50547
+ }
50548
+ shown = {
50549
+ ...reading,
50550
+ totals: added.totals,
50551
+ coveredMs: Math.max(0, reading.coveredMs - mark.coveredMs)
50552
+ };
50553
+ const ago = now - mark.takenAt;
50554
+ sinceFields = {
50555
+ since: "mark",
50556
+ markSetMsAgo: ago,
50557
+ ...added.shrunk > 0 ? { lessThanAtMark: added.shrunk } : {}
50558
+ };
50559
+ lead.push(`Counted from the mark set ${ago}ms ago: each function's calls and milliseconds are what it added after the mark, frames and coveredMs are those since it, and a function that added no call is left out.`);
50560
+ if (added.shrunk > 0) {
50561
+ const one = added.shrunk === 1;
50562
+ lead.push(`${one ? "One function reads" : `${added.shrunk} functions read`} less than at the mark and ${one ? "is" : "are"} left out, counted under lessThanAtMark: the engine's own totals arrived after the mark and replaced the frames' sum, and capped totals leave out the functions with the least time.`);
50563
+ }
50564
+ }
50565
+ if (asked.mark) {
50566
+ if (reading.totals === null) {
50567
+ return this.createErrorResponse("There is nothing to mark yet: the game has not connected to the profiler.", ["Ask again in a moment, once the game is up"]);
50568
+ }
50569
+ const mark = {
50570
+ takenAt: now,
50571
+ projectPath: run.projectPath,
50572
+ runStartedAt: run.startedAt,
50573
+ coveredMs: reading.coveredMs,
50574
+ totals: reading.totals
50575
+ };
50576
+ if (!writeProfileMark(markPath, mark)) {
50577
+ return this.createErrorResponse(`The mark could not be written to ${markPath}, so none is set.`);
50578
+ }
50579
+ lead.push(asked.since ? 'The mark is moved to the totals as they are now, so the next since: "mark" counts from here.' : 'The mark is set at these totals: editor_run profile with since: "mark" answers only what is added after it.');
50580
+ }
50581
+ const answer = profileAnswer(shown, limit);
50582
+ const note = [...lead, ...typeof answer["note"] === "string" ? [answer["note"]] : []].join(" ");
50583
+ return this.jsonTextResponse({
50584
+ ...answer,
50585
+ ...sinceFields,
50586
+ ...asked.mark ? { marked: true } : {},
50587
+ note: note === "" ? undefined : note
50588
+ });
50143
50589
  }
50144
50590
  async firstProfiledFrame() {
50145
50591
  const deadline = Date.now() + PROFILER_ON_MS;
@@ -50340,7 +50786,7 @@ class GodotServer {
50340
50786
  notes.push(endedWithoutACode({
50341
50787
  throughEditor: run.throughEditor,
50342
50788
  announced: announced !== undefined || run.announcedPid !== undefined,
50343
- couldAnnounce: run.projectPath !== null && existsSync23(join30(run.projectPath, RUNTIME_AUTOLOAD.path)),
50789
+ couldAnnounce: run.projectPath !== null && existsSync25(join32(run.projectPath, RUNTIME_AUTOLOAD.path)),
50344
50790
  endedHere: typeof run.endedHere === "string",
50345
50791
  pickedUp: run.pickedUp === true
50346
50792
  }));
@@ -50970,7 +51416,7 @@ class GodotServer {
50970
51416
  if (!scriptPath.ok) {
50971
51417
  return this.createErrorResponse(scriptPath.reason);
50972
51418
  }
50973
- if (!existsSync23(scriptPath.absolutePath)) {
51419
+ if (!existsSync25(scriptPath.absolutePath)) {
50974
51420
  return this.createErrorResponse(`Script file does not exist: ${scriptPath.absolutePath}`);
50975
51421
  }
50976
51422
  const asked = readNonEmptyString(args, "newScriptPath");
@@ -51384,7 +51830,7 @@ class GodotServer {
51384
51830
  }
51385
51831
  const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
51386
51832
  const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
51387
- if (declared.test(readFileSync26(scriptPath, "utf8"))) {
51833
+ if (declared.test(readFileSync27(scriptPath, "utf8"))) {
51388
51834
  return null;
51389
51835
  }
51390
51836
  const checked = await this.classesTheEditorCannotSee(args);
@@ -51552,7 +51998,7 @@ class GodotServer {
51552
51998
  async ownRunOnItsWay(projectPath, budgetMs) {
51553
51999
  const run = await this.currentRun();
51554
52000
  const project = projectPath ?? run?.projectPath ?? null;
51555
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync23(join30(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
52001
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync25(join32(project, RUNTIME_AUTOLOAD.path)) || !await this.runStillGoing(run)) {
51556
52002
  return null;
51557
52003
  }
51558
52004
  const already = runtimesAnnounced().running;
@@ -51561,7 +52007,7 @@ class GodotServer {
51561
52007
  return !run.announcedBefore.has(one.pid);
51562
52008
  }
51563
52009
  try {
51564
- return statSync13(one.file).mtimeMs >= run.startedAt;
52010
+ return statSync14(one.file).mtimeMs >= run.startedAt;
51565
52011
  } catch {
51566
52012
  return false;
51567
52013
  }
@@ -51642,7 +52088,7 @@ class GodotServer {
51642
52088
  }
51643
52089
  }
51644
52090
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
51645
- const screenshotPath = screenshotDir ? join30(screenshotDir, "capture.png") : null;
52091
+ const screenshotPath = screenshotDir ? join32(screenshotDir, "capture.png") : null;
51646
52092
  const staleNote = this.runtimeStaleNote(choice.endpoint);
51647
52093
  const stale = staleNote === undefined ? {} : { addonIsStale: true, staleNote };
51648
52094
  const refused = (message) => this.createErrorResponse(staleNote === undefined ? message : `${message}
@@ -51661,8 +52107,10 @@ ${staleNote}`);
51661
52107
  }
51662
52108
  this.callsOnGames.add(onGame);
51663
52109
  const alongside = () => onGame.alongside.size === 0 ? {} : { ranAlongside: [...onGame.alongside], alongsideNote: ALONGSIDE_NOTE };
52110
+ let savedLater = false;
51664
52111
  try {
51665
- const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, !expectsScreenshot);
52112
+ const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, expectsScreenshot ? () => discard(screenshotDir) : "keep");
52113
+ savedLater = !reply.ok && reply.stillWithTheGame === true;
51666
52114
  if (!reply.ok && reply.requestId !== undefined) {
51667
52115
  return this.jsonTextResponse({
51668
52116
  pending: true,
@@ -51708,12 +52156,14 @@ Ran alongside ${ranWith.join(", ")}. ${ALONGSIDE_NOTE}`}${staleNote === undefine
51708
52156
 
51709
52157
  ${staleNote}`}`
51710
52158
  },
51711
- { type: "image", data: readFileSync26(screenshotPath).toString("base64"), mimeType: "image/png" }
52159
+ { type: "image", data: readFileSync27(screenshotPath).toString("base64"), mimeType: "image/png" }
51712
52160
  ]
51713
52161
  };
51714
52162
  } finally {
51715
52163
  this.callsOnGames.delete(onGame);
51716
- discard(screenshotDir);
52164
+ if (!savedLater) {
52165
+ discard(screenshotDir);
52166
+ }
51717
52167
  }
51718
52168
  }
51719
52169
  handleLateAnswer(args) {
@@ -51823,12 +52273,13 @@ async function runGodotServer() {
51823
52273
  const server = new GodotServer;
51824
52274
  await server.run();
51825
52275
  }
51826
- var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __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, CLOSE_ASK_MS = 3000, 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, 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, OUTPUT_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;
52276
+ var UPDATE_NOTICE_EVERY = 500, FEEDBACK_NOTICE_EVERY = 250, FAILED_BUDGET = 1e6, ANSWER_BUDGET = 4000000, __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, CLOSE_ASK_MS = 3000, 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, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_HEARTBEAT_MS = 20000, DEPENDED_FAILED = "Failed to compile depended scripts", LOOPING_AFTER = 100, WAIT_FOR_RUN_MS = 600000, RUN_POLL_MS = 250, PATH_SOLUTIONS, OUTPUT_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;
51827
52277
  var init_server = __esm(() => {
51828
52278
  init_dist();
51829
52279
  init_stdio();
51830
52280
  init_alive();
51831
52281
  init_answer_json();
52282
+ init_autoload_failure();
51832
52283
  init_boot_note();
51833
52284
  init_breakpoint_note();
51834
52285
  init_bridge_announce();
@@ -51839,6 +52290,7 @@ var init_server = __esm(() => {
51839
52290
  init_cut_test_run();
51840
52291
  init_dap_client();
51841
52292
  init_desktop();
52293
+ init_doc_cache();
51842
52294
  init_editor_log();
51843
52295
  init_engine_run();
51844
52296
  init_errors();
@@ -51881,7 +52333,7 @@ var init_server = __esm(() => {
51881
52333
  init_tool_definitions();
51882
52334
  init_tool_reference();
51883
52335
  init_update_check();
51884
- __dirname2 = dirname22(fileURLToPath5(import.meta.url));
52336
+ __dirname2 = dirname23(fileURLToPath5(import.meta.url));
51885
52337
  KEEPER_DEBUG_MS = PAUSE_MS + STEP_MS + 2 * READ_MS;
51886
52338
  CONSOLE_WRAPPER = /_console(\.exe)?$/i;
51887
52339
  LONGEST_BOOT_NOTED_MS = 5 * 60000;
@@ -51941,8 +52393,8 @@ var init_server = __esm(() => {
51941
52393
  init_bridge_announce();
51942
52394
  init_errors();
51943
52395
  init_godot_path();
51944
- import { existsSync as existsSync24 } from "node:fs";
51945
- import { join as join31, resolve as resolve8 } from "node:path";
52396
+ import { existsSync as existsSync26 } from "node:fs";
52397
+ import { join as join33, resolve as resolve8 } from "node:path";
51946
52398
 
51947
52399
  // src/harnesses.ts
51948
52400
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
@@ -52877,10 +53329,10 @@ init_issues();
52877
53329
  init_paths();
52878
53330
 
52879
53331
  // src/prompt.ts
52880
- import process8 from "node:process";
53332
+ import process9 from "node:process";
52881
53333
  import { createInterface } from "node:readline/promises";
52882
53334
  function interactive() {
52883
- return process8.stdin.isTTY && process8.stdout.isTTY;
53335
+ return process9.stdin.isTTY && process9.stdout.isTTY;
52884
53336
  }
52885
53337
 
52886
53338
  class Ask {
@@ -52889,7 +53341,7 @@ class Ask {
52889
53341
  if (!interactive()) {
52890
53342
  return fallback;
52891
53343
  }
52892
- this.line ??= createInterface({ input: process8.stdin, output: process8.stdout });
53344
+ this.line ??= createInterface({ input: process9.stdin, output: process9.stdout });
52893
53345
  const answer = (await this.line.question(`${question} ${fallback ? "[Y/n]" : "[y/N]"} `)).trim();
52894
53346
  return answer === "" ? fallback : answer.toLowerCase().startsWith("y");
52895
53347
  }
@@ -52907,10 +53359,10 @@ init_setup();
52907
53359
  init_setup();
52908
53360
  init_tool_definitions();
52909
53361
  init_tool_reference();
52910
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
52911
- import { basename as basename5, dirname as dirname7, join as join13 } from "node:path";
53362
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync9, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
53363
+ import { basename as basename6, dirname as dirname8, join as join14 } from "node:path";
52912
53364
  var SKILL_NAME = "gdharness";
52913
- var SHARED_SKILLS = join13(".agents", "skills");
53365
+ var SHARED_SKILLS = join14(".agents", "skills");
52914
53366
  function skillMarkdown(version) {
52915
53367
  return `---
52916
53368
  name: ${SKILL_NAME}
@@ -52997,7 +53449,7 @@ that is running, and the project on disk. ${TOOL_SPECS.length} tools, named \`do
52997
53449
  | What every label on a screen says | \`runtime_inspect tree\` with \`properties\` naming \`text\`, which reads it off every node that has one; \`includeProperties\` is every stored property of every node and ran to seventy-six thousand characters for one row |
52998
53450
  | Whether this engine has a method or property, and what it takes | \`editor_classes info\` with \`member\`, which answers that one member and which class declares it rather than the whole class |
52999
53451
  | Wait for a run to finish | \`editor_run wait\`, never a sleep or a shell loop on the pid: it answers as \`editor_output\` does, once the run is over |
53000
- | Find which GDScript makes a run slow | \`editor_run start\` with \`profile\` set, then \`editor_run profile\`: the engine's own script profiler, per function with calls and self and total milliseconds, by self time, never \`Time.get_ticks_usec()\` laps edited into the game. A scene that does its work in \`_ready\` and quits is covered whole when the runtime autoload is registered, and a game the editor is already playing is profiled from the moment it is asked. \`callersOf\` names where the project's scripts call one function, each place with its function's numbers, since the engine records no callers |
53452
+ | Find which GDScript makes a run slow | \`editor_run start\` with \`profile\` set, then \`editor_run profile\`: the engine's own script profiler, per function with calls and self and total milliseconds, by self time, never \`Time.get_ticks_usec()\` laps edited into the game. A scene that does its work in \`_ready\` and quits is covered whole when the runtime autoload is registered, and a game the editor is already playing is profiled from the moment it is asked. For one action, a press or a commit, set \`mark\` on a profile before it and pass \`since: "mark"\` after it, which answers only what the action added. \`callersOf\` names where the project's scripts call one function, each place with its function's numbers, since the engine records no callers |
53001
53453
  | End a bench and the workers it opened | \`editor_run stop\` with \`andChildren\`, never a process listing by scene name: it ends every process under the run's, to any depth, that is a game of the project, announced as one or the project's engine run with \`--path\` on it, names them under \`endedChildren\` with what identified each, and leaves and names any other |
53002
53454
  | End a game of the project that no server is holding | \`editor_run stop\` with its \`pid\` from \`editor_status\` \`runtimes\`, never a process kill by hand: it is ended only when it announced itself for the project and its command line is the project's engine run |
53003
53455
  | Play a game without touching the player's saves | \`editor_run start\` with \`savesIn\`, a directory for this run's \`user://\`: the editor plays with its own environment, so a run given one is started by the server, and the answer says under \`savesNote\` when the platform ignores it |
@@ -53135,27 +53587,27 @@ what they report while starting comes back under \`engine_messages_before_operat
53135
53587
  function skillFiles(version) {
53136
53588
  return new Map([
53137
53589
  ["SKILL.md", skillMarkdown(version)],
53138
- [join13("references", "tools.md"), renderToolsMarkdown()]
53590
+ [join14("references", "tools.md"), renderToolsMarkdown()]
53139
53591
  ]);
53140
53592
  }
53141
53593
  function skillDirectories(harnesses, projectPath, exists) {
53142
- const directories = [join13(projectPath, SHARED_SKILLS, SKILL_NAME)];
53594
+ const directories = [join14(projectPath, SHARED_SKILLS, SKILL_NAME)];
53143
53595
  for (const harness of harnesses) {
53144
53596
  if (harness.skills === undefined) {
53145
53597
  continue;
53146
53598
  }
53147
- const own = join13(projectPath, harness.skills.dir);
53148
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join13(own, SKILL_NAME))) {
53149
- directories.push(join13(own, SKILL_NAME));
53599
+ const own = join14(projectPath, harness.skills.dir);
53600
+ if ((!harness.skills.shared || exists(own)) && !directories.includes(join14(own, SKILL_NAME))) {
53601
+ directories.push(join14(own, SKILL_NAME));
53150
53602
  }
53151
53603
  }
53152
53604
  return directories;
53153
53605
  }
53154
53606
  function everySkillDirectory(projectPath, harnesses) {
53155
- const directories = new Set([join13(projectPath, SHARED_SKILLS, SKILL_NAME)]);
53607
+ const directories = new Set([join14(projectPath, SHARED_SKILLS, SKILL_NAME)]);
53156
53608
  for (const harness of harnesses) {
53157
53609
  if (harness.skills !== undefined) {
53158
- directories.add(join13(projectPath, harness.skills.dir, SKILL_NAME));
53610
+ directories.add(join14(projectPath, harness.skills.dir, SKILL_NAME));
53159
53611
  }
53160
53612
  }
53161
53613
  return [...directories];
@@ -53163,33 +53615,33 @@ function everySkillDirectory(projectPath, harnesses) {
53163
53615
  function removeSkill(directories, projectPath) {
53164
53616
  const removed = [];
53165
53617
  for (const directory of directories) {
53166
- if (existsSync13(directory)) {
53618
+ if (existsSync14(directory)) {
53167
53619
  rmSync6(directory, { recursive: true, force: true });
53168
- pruneEmpty(dirname7(directory), projectPath);
53620
+ pruneEmpty(dirname8(directory), projectPath);
53169
53621
  removed.push(directory);
53170
53622
  }
53171
53623
  }
53172
53624
  return removed;
53173
53625
  }
53174
53626
  function pruneEmpty(directory, projectPath) {
53175
- const shared = join13(projectPath, ".agents");
53627
+ const shared = join14(projectPath, ".agents");
53176
53628
  let path = directory;
53177
- while (path !== projectPath && (basename5(path) === "skills" || path === shared)) {
53178
- if (readdirSync8(path).length > 0) {
53629
+ while (path !== projectPath && (basename6(path) === "skills" || path === shared)) {
53630
+ if (readdirSync9(path).length > 0) {
53179
53631
  return;
53180
53632
  }
53181
53633
  rmSync6(path, { recursive: true, force: true });
53182
- path = dirname7(path);
53634
+ path = dirname8(path);
53183
53635
  }
53184
53636
  }
53185
53637
  function writeSkill(directories, version) {
53186
53638
  const files = skillFiles(version);
53187
53639
  return directories.map((directory) => {
53188
- const replaced = existsSync13(directory);
53640
+ const replaced = existsSync14(directory);
53189
53641
  rmSync6(directory, { recursive: true, force: true });
53190
53642
  for (const [name, contents] of files) {
53191
- const path = join13(directory, name);
53192
- mkdirSync5(dirname7(path), { recursive: true });
53643
+ const path = join14(directory, name);
53644
+ mkdirSync5(dirname8(path), { recursive: true });
53193
53645
  writeFileSync7(path, contents, "utf8");
53194
53646
  }
53195
53647
  return { path: directory, replaced };
@@ -53198,13 +53650,13 @@ function writeSkill(directories, version) {
53198
53650
 
53199
53651
  // src/version.ts
53200
53652
  import { readFileSync as readFileSync12 } from "node:fs";
53201
- import { dirname as dirname8, join as join14 } from "node:path";
53653
+ import { dirname as dirname9, join as join15 } from "node:path";
53202
53654
  import { fileURLToPath as fileURLToPath2 } from "node:url";
53203
53655
  function getLocalVersion() {
53204
- const currentDirectory = dirname8(fileURLToPath2(import.meta.url));
53656
+ const currentDirectory = dirname9(fileURLToPath2(import.meta.url));
53205
53657
  const packageCandidates = [
53206
- join14(currentDirectory, "..", "package.json"),
53207
- join14(currentDirectory, "..", "..", "package.json")
53658
+ join15(currentDirectory, "..", "package.json"),
53659
+ join15(currentDirectory, "..", "..", "package.json")
53208
53660
  ];
53209
53661
  for (const packagePath of packageCandidates) {
53210
53662
  try {
@@ -53226,7 +53678,7 @@ class UsageError extends Refusal {
53226
53678
  function projectArgument(at) {
53227
53679
  const named = args.slice(at).find((value) => !value.startsWith("--"));
53228
53680
  const projectPath = resolve8(named ?? ".");
53229
- if (!existsSync24(join31(projectPath, "project.godot"))) {
53681
+ if (!existsSync26(join33(projectPath, "project.godot"))) {
53230
53682
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
53231
53683
  }
53232
53684
  return projectPath;
@@ -53271,7 +53723,7 @@ async function registerRuntime(godot, projectPath) {
53271
53723
  return;
53272
53724
  }
53273
53725
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
53274
- if (!existsSync24(join31(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
53726
+ if (!existsSync26(join33(projectPath, (named ?? "").replace(/^res:\/\//, "")))) {
53275
53727
  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}`);
53276
53728
  }
53277
53729
  }
@@ -53403,7 +53855,7 @@ async function setup() {
53403
53855
  console.log("no harness configured; name one yourself, and gdharness harnesses lists them all");
53404
53856
  }
53405
53857
  if (!args.includes("--no-skill")) {
53406
- const directories = skillDirectories(chosen.harnesses, projectPath, existsSync24);
53858
+ const directories = skillDirectories(chosen.harnesses, projectPath, existsSync26);
53407
53859
  for (const written of writeSkill(directories, getLocalVersion())) {
53408
53860
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
53409
53861
  }
@@ -53517,7 +53969,7 @@ async function upgrade() {
53517
53969
  moved.push(took.from);
53518
53970
  }
53519
53971
  }
53520
- for (const written of writeSkill(skillDirectories(already, projectPath, existsSync24), version)) {
53972
+ for (const written of writeSkill(skillDirectories(already, projectPath, existsSync26), version)) {
53521
53973
  console.log(`skill: ${written.replaced ? "replaced" : "written"} ${written.path}`);
53522
53974
  }
53523
53975
  console.log(`