gdharness 1.3.43 → 1.3.45

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
@@ -9723,8 +9723,71 @@ var init_gdscript_source = __esm(() => {
9723
9723
  ]);
9724
9724
  });
9725
9725
 
9726
+ // src/whole-file.ts
9727
+ import { readFileSync as readFileSync5, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
9728
+ function readWhole(path) {
9729
+ const until = Date.now() + HELD_FILE_MS;
9730
+ for (;; ) {
9731
+ try {
9732
+ return readFileSync5(path, "utf8");
9733
+ } catch (error) {
9734
+ const code = error.code ?? "";
9735
+ if (code === "ENOENT") {
9736
+ return null;
9737
+ }
9738
+ if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
9739
+ throw error;
9740
+ }
9741
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
9742
+ }
9743
+ }
9744
+ }
9745
+ function writeWhole(path, text) {
9746
+ const partial = `${path}.${process.pid}.partial`;
9747
+ try {
9748
+ writeFileSync4(partial, text, "utf8");
9749
+ } catch {
9750
+ return false;
9751
+ }
9752
+ const until = Date.now() + HELD_FILE_MS;
9753
+ for (;; ) {
9754
+ try {
9755
+ renameSync(partial, path);
9756
+ return true;
9757
+ } catch (error) {
9758
+ const code = error.code ?? "";
9759
+ if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
9760
+ try {
9761
+ rmSync4(partial, { force: true });
9762
+ } catch {}
9763
+ return false;
9764
+ }
9765
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
9766
+ }
9767
+ }
9768
+ }
9769
+ function removeWhole(path) {
9770
+ const until = Date.now() + HELD_FILE_MS;
9771
+ for (;; ) {
9772
+ try {
9773
+ rmSync4(path, { force: true });
9774
+ return true;
9775
+ } catch (error) {
9776
+ const code = error.code ?? "";
9777
+ if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
9778
+ return false;
9779
+ }
9780
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
9781
+ }
9782
+ }
9783
+ }
9784
+ var HELD_FILE_MS = 1000, HELD_FILE_RETRY_MS = 10, HELD_BY_A_READER;
9785
+ var init_whole_file = __esm(() => {
9786
+ HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
9787
+ });
9788
+
9726
9789
  // src/class-cache.ts
9727
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync4 } from "node:fs";
9790
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
9728
9791
  import { join as join6, posix } from "node:path";
9729
9792
  function isToolScript(source) {
9730
9793
  return TOOL.test(codeOf(source));
@@ -9799,7 +9862,7 @@ function eachScript(projectPath, each) {
9799
9862
  if (entry.isDirectory()) {
9800
9863
  visit(path, `${prefix}${entry.name}/`);
9801
9864
  } else if (entry.isFile() && entry.name.endsWith(".gd")) {
9802
- each(`res://${prefix}${entry.name}`, readFileSync5(path, "utf8"));
9865
+ each(`res://${prefix}${entry.name}`, readFileSync6(path, "utf8"));
9803
9866
  }
9804
9867
  }
9805
9868
  };
@@ -9832,12 +9895,11 @@ function scriptsDeclaring(projectPath, classes) {
9832
9895
  return declaring.sort((a, b) => a.script.localeCompare(b.script));
9833
9896
  }
9834
9897
  function cachedClasses(projectPath) {
9835
- const cache = join6(projectPath, ".godot", "global_script_class_cache.cfg");
9836
- if (!existsSync4(cache)) {
9898
+ const text = readWhole(join6(projectPath, ".godot", "global_script_class_cache.cfg"));
9899
+ if (text === null) {
9837
9900
  return null;
9838
9901
  }
9839
9902
  const listed = new Map;
9840
- const text = readFileSync5(cache, "utf8");
9841
9903
  for (const entry of text.matchAll(/"class":\s*&"([^"]+)"[\s\S]*?"path":\s*"([^"]+)"/g)) {
9842
9904
  listed.set(entry[1] ?? "", entry[2] ?? "");
9843
9905
  }
@@ -10126,7 +10188,8 @@ function failedReloadNote(failed) {
10126
10188
  } else {
10127
10189
  copy = `The copy the editor holds was compiled again from the text it was built from and changed even so${lost.length === 0 ? "" : `, and has lost ${lost.join(", ")}`}: anything diagnosed against it now is answered from what is left, and reloading it once it compiles rebuilds it.`;
10128
10190
  }
10129
- const order = failed.builtOn !== undefined ? ` ${failed.builtOn}` : failed.names.length === 0 ? "" : ` If they deny a member of ${failed.names.join(" or ")} that its file declares, the editor's copy of that class is behind: reload that class first with reloadScript and then this script, the order that cleared it in the one project that has met this.`;
10191
+ const advice = failed.names.length === 0 ? "" : ` If they deny a member of ${failed.names.join(" or ")} that its file declares, the editor's copy of that class is behind: reload that class first with reloadScript and then this script, the order that cleared it in the one project that has met this.`;
10192
+ const order = failed.builtOn === undefined ? advice : ` ${failed.builtOn}${failed.builtOnUnchecked === true ? advice : ""}`;
10130
10193
  return `${failed.script} did not compile: Godot answered ${answered}. ${copy} ` + `script_diagnostics on it gives the errors.${order}`;
10131
10194
  }
10132
10195
  function uncachedClassNote(types) {
@@ -10188,6 +10251,7 @@ function unseenByEditor(projectPath, editorHolds) {
10188
10251
  var DECLARATION, TOOL, ABSTRACT_HEADER, UNKNOWN_CLASS;
10189
10252
  var init_class_cache = __esm(() => {
10190
10253
  init_gdscript_source();
10254
+ init_whole_file();
10191
10255
  DECLARATION = /^(?:@[a-z_]+(?:\([^)\n]*\))?\s+)*class_name\s+([A-Za-z_][A-Za-z0-9_]*)/m;
10192
10256
  TOOL = /^[ \t]*(?:@[a-z_]+(?:\([^)\n]*\))?[ \t]+)*@tool\b/m;
10193
10257
  ABSTRACT_HEADER = /^((?:@[\p{L}_][\p{L}\p{N}_]*(?:\([^)]*\))?\s+)*)(?:class_name|extends)\s/mu;
@@ -10894,7 +10958,7 @@ var init_junit = __esm(() => {
10894
10958
  });
10895
10959
 
10896
10960
  // src/suite-judge.ts
10897
- import { readFileSync as readFileSync6 } from "node:fs";
10961
+ import { readFileSync as readFileSync7 } from "node:fs";
10898
10962
  import { stripVTControlCharacters } from "node:util";
10899
10963
  function secondsOf(time) {
10900
10964
  let ms = 0;
@@ -11095,7 +11159,7 @@ function versionFault(said, wanted) {
11095
11159
  function readExpected(file) {
11096
11160
  const declared = (value) => typeof value === "object" && value !== null ? value : {};
11097
11161
  try {
11098
- const parsed = JSON.parse(readFileSync6(file, "utf8"));
11162
+ const parsed = JSON.parse(readFileSync7(file, "utf8"));
11099
11163
  const read = typeof parsed === "object" && parsed !== null ? parsed : {};
11100
11164
  return { errors: declared(read["errors"]), warnings: declared(read["warnings"]) };
11101
11165
  } catch {
@@ -11417,9 +11481,9 @@ import {
11417
11481
  openSync as openSync2,
11418
11482
  readdirSync as readdirSync3,
11419
11483
  readSync,
11420
- rmSync as rmSync4,
11484
+ rmSync as rmSync5,
11421
11485
  statSync as statSync5,
11422
- writeFileSync as writeFileSync4,
11486
+ writeFileSync as writeFileSync5,
11423
11487
  writeSync as writeSync2
11424
11488
  } from "node:fs";
11425
11489
  import { availableParallelism, freemem, totalmem } from "node:os";
@@ -11495,14 +11559,14 @@ function sweepRunDirectories(runs, kept = KEPT_RUN_DIRECTORIES) {
11495
11559
  const named = /^(\d+)-(\d+)$/.exec(entry);
11496
11560
  const pid = Number(named?.[2]);
11497
11561
  if (!Number.isInteger(pid)) {
11498
- rmSync4(join7(runs, entry), { recursive: true, force: true });
11562
+ rmSync5(join7(runs, entry), { recursive: true, force: true });
11499
11563
  } else if (!alive(pid)) {
11500
11564
  ended.push({ entry, started: Number(named?.[1]) });
11501
11565
  }
11502
11566
  }
11503
11567
  ended.sort((a, b) => b.started - a.started);
11504
11568
  for (const { entry } of ended.slice(kept)) {
11505
- rmSync4(join7(runs, entry), { recursive: true, force: true });
11569
+ rmSync5(join7(runs, entry), { recursive: true, force: true });
11506
11570
  }
11507
11571
  }
11508
11572
  function roomFor(most, processors, available, said = "of memory free", processorsSaid = `the machine has ${plural(processors, "processor")}`) {
@@ -11607,7 +11671,7 @@ async function watchedEngine(launch, args, directory, log, silenceSeconds, silen
11607
11671
  piped = [helped.stdout, helped.stderr];
11608
11672
  child = helped;
11609
11673
  stop = () => {
11610
- writeFileSync4(stopFile, "");
11674
+ writeFileSync5(stopFile, "");
11611
11675
  };
11612
11676
  } else {
11613
11677
  const held = heldToCores(launch.cores, launch.godotPath, args);
@@ -11754,6 +11818,16 @@ var init_engine_launch = __esm(() => {
11754
11818
  MEMORY_PER_ENGINE = 1.5 * 1024 ** 3;
11755
11819
  });
11756
11820
 
11821
+ // src/cache-home.ts
11822
+ import { homedir as homedir2 } from "node:os";
11823
+ import { join as join8 } from "node:path";
11824
+ function cacheHome(env = process.env, platform = process.platform, home = homedir2()) {
11825
+ const set = (name) => env[name] === "" ? undefined : env[name];
11826
+ const local = platform === "win32" ? set("LOCALAPPDATA") : undefined;
11827
+ return join8(set("XDG_CACHE_HOME") ?? local ?? join8(home, ".cache"), "gdharness");
11828
+ }
11829
+ var init_cache_home = () => {};
11830
+
11757
11831
  // src/call-signal.ts
11758
11832
  import { AsyncLocalStorage } from "node:async_hooks";
11759
11833
  function stillGoingSaid(tool, op, elapsedMs) {
@@ -11761,9 +11835,9 @@ function stillGoingSaid(tool, op, elapsedMs) {
11761
11835
  const took = seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
11762
11836
  return `${op === null || op === "" ? tool : `${tool} ${op}`} is still going; ${took} in.`;
11763
11837
  }
11764
- async function withCallSignal(signal, answer, progress, still) {
11838
+ async function withCallSignal(signal, answer, progress, still, slotWaits) {
11765
11839
  if (progress === undefined) {
11766
- return signal === undefined ? answer() : current.run({ signal, progress: undefined }, answer);
11840
+ return signal === undefined && slotWaits === undefined ? answer() : current.run({ signal, progress: undefined, slotWaits }, answer);
11767
11841
  }
11768
11842
  const began = Date.now();
11769
11843
  const rising = risingOnly(progress);
@@ -11787,7 +11861,7 @@ async function withCallSignal(signal, answer, progress, still) {
11787
11861
  };
11788
11862
  rearm();
11789
11863
  try {
11790
- return await current.run({ signal, progress: told }, answer);
11864
+ return await current.run({ signal, progress: told, slotWaits }, answer);
11791
11865
  } finally {
11792
11866
  going = undefined;
11793
11867
  clearTimeout(quiet);
@@ -11806,70 +11880,27 @@ function callSignal() {
11806
11880
  function callProgress() {
11807
11881
  return current.getStore()?.progress;
11808
11882
  }
11883
+ function callSlotWaits() {
11884
+ return current.getStore()?.slotWaits;
11885
+ }
11809
11886
  var current, CALL_HEARTBEAT_MS = 20000;
11810
11887
  var init_call_signal = __esm(() => {
11811
11888
  current = new AsyncLocalStorage;
11812
11889
  });
11813
11890
 
11814
- // src/whole-file.ts
11815
- import { renameSync, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
11816
- function writeWhole(path, text) {
11817
- const partial = `${path}.${process.pid}.partial`;
11818
- try {
11819
- writeFileSync5(partial, text, "utf8");
11820
- } catch {
11821
- return false;
11822
- }
11823
- const until = Date.now() + HELD_FILE_MS;
11824
- for (;; ) {
11825
- try {
11826
- renameSync(partial, path);
11827
- return true;
11828
- } catch (error) {
11829
- const code = error.code ?? "";
11830
- if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
11831
- try {
11832
- rmSync5(partial, { force: true });
11833
- } catch {}
11834
- return false;
11835
- }
11836
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
11837
- }
11838
- }
11839
- }
11840
- function removeWhole(path) {
11841
- const until = Date.now() + HELD_FILE_MS;
11842
- for (;; ) {
11843
- try {
11844
- rmSync5(path, { force: true });
11845
- return true;
11846
- } catch (error) {
11847
- const code = error.code ?? "";
11848
- if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
11849
- return false;
11850
- }
11851
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
11852
- }
11853
- }
11854
- }
11855
- var HELD_FILE_MS = 1000, HELD_FILE_RETRY_MS = 10, HELD_BY_A_READER;
11856
- var init_whole_file = __esm(() => {
11857
- HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
11858
- });
11859
-
11860
11891
  // src/engine-slots.ts
11861
11892
  import {
11862
11893
  closeSync as closeSync3,
11863
11894
  mkdirSync as mkdirSync3,
11864
11895
  openSync as openSync3,
11865
- readFileSync as readFileSync7,
11896
+ readFileSync as readFileSync8,
11866
11897
  rmSync as rmSync6,
11867
11898
  statSync as statSync6,
11868
11899
  watch,
11869
11900
  writeSync as writeSync3
11870
11901
  } from "node:fs";
11871
- import { availableParallelism as availableParallelism2, homedir as homedir2 } from "node:os";
11872
- import { join as join8, resolve as resolve4 } from "node:path";
11902
+ import { availableParallelism as availableParallelism2, homedir as homedir3 } from "node:os";
11903
+ import { join as join9, resolve as resolve4 } from "node:path";
11873
11904
  import process7 from "node:process";
11874
11905
  import { setTimeout as delay3 } from "node:timers/promises";
11875
11906
  function machineSlots(asked = {
@@ -11879,15 +11910,13 @@ function machineSlots(asked = {
11879
11910
  env: process7.env,
11880
11911
  platform: process7.platform,
11881
11912
  processors: availableParallelism2(),
11882
- home: homedir2(),
11913
+ home: homedir3(),
11883
11914
  cwd: process7.cwd()
11884
11915
  }) {
11885
11916
  const { env } = machine;
11886
11917
  const named = nonEmpty(env[SLOTS_VARIABLE]);
11887
- const xdg = nonEmpty(env["XDG_CACHE_HOME"]);
11888
- const local = machine.platform === "win32" ? nonEmpty(env["LOCALAPPDATA"]) : undefined;
11889
- const cache = join8(xdg ?? local ?? join8(machine.home, ".cache"), "gdharness");
11890
- const directory = asked.directory ?? (named === undefined ? join8(cache, "slots") : resolve4(machine.cwd, named));
11918
+ const cache = cacheHome(env, machine.platform, machine.home);
11919
+ const directory = asked.directory ?? (named === undefined ? join9(cache, "slots") : resolve4(machine.cwd, named));
11891
11920
  const counted = nonEmpty(env[SLOT_COUNT_VARIABLE]);
11892
11921
  let count = asked.count;
11893
11922
  if (count === null && counted !== undefined) {
@@ -11992,7 +12021,7 @@ class EngineSlots {
11992
12021
  this.holder = holder;
11993
12022
  this.say = say;
11994
12023
  }
11995
- async take(signal, waiting) {
12024
+ async take(signal, waiting, blocked) {
11996
12025
  mkdirSync3(this.directory, { recursive: true });
11997
12026
  const began = Date.now();
11998
12027
  let said = Number.NEGATIVE_INFINITY;
@@ -12005,7 +12034,10 @@ class EngineSlots {
12005
12034
  if (lock !== null) {
12006
12035
  return lock;
12007
12036
  }
12008
- mark ??= this.waitMarked();
12037
+ if (mark === undefined) {
12038
+ mark = this.waitMarked();
12039
+ blocked?.();
12040
+ }
12009
12041
  const now = Date.now();
12010
12042
  if (waiting !== undefined) {
12011
12043
  if (now - said >= WAIT_SAID_MS) {
@@ -12032,7 +12064,7 @@ class EngineSlots {
12032
12064
  async takeFree() {
12033
12065
  mkdirSync3(this.directory, { recursive: true });
12034
12066
  for (let slot = 0;slot < this.count; slot += 1) {
12035
- const lock = join8(this.directory, `${String(slot)}.lock`);
12067
+ const lock = join9(this.directory, `${String(slot)}.lock`);
12036
12068
  if (this.made(lock) || await this.takenOver(lock)) {
12037
12069
  this.held.add(lock);
12038
12070
  return lock;
@@ -12041,9 +12073,9 @@ class EngineSlots {
12041
12073
  return null;
12042
12074
  }
12043
12075
  waitMarked() {
12044
- const marks = join8(this.directory, WAITING_FOLDER);
12076
+ const marks = join9(this.directory, WAITING_FOLDER);
12045
12077
  waitsMarked += 1;
12046
- const mark = join8(marks, `${String(this.holder.pid)}-${String(waitsMarked)}.json`);
12078
+ const mark = join9(marks, `${String(this.holder.pid)}-${String(waitsMarked)}.json`);
12047
12079
  try {
12048
12080
  mkdirSync3(marks, { recursive: true });
12049
12081
  } catch {
@@ -12059,7 +12091,7 @@ class EngineSlots {
12059
12091
  const found = [];
12060
12092
  for (let slot = 0;slot < this.count; slot += 1) {
12061
12093
  try {
12062
- const holder = holderOf(readFileSync7(join8(this.directory, `${String(slot)}.lock`), "utf8"));
12094
+ const holder = holderOf(readFileSync8(join9(this.directory, `${String(slot)}.lock`), "utf8"));
12063
12095
  if (holder !== null) {
12064
12096
  found.push(holder);
12065
12097
  }
@@ -12079,7 +12111,7 @@ class EngineSlots {
12079
12111
  release(lock) {
12080
12112
  this.held.delete(lock);
12081
12113
  try {
12082
- if (holderOf(readFileSync7(lock, "utf8"))?.pid === this.holder.pid) {
12114
+ if (holderOf(readFileSync8(lock, "utf8"))?.pid === this.holder.pid) {
12083
12115
  removeWhole(lock);
12084
12116
  }
12085
12117
  } catch {}
@@ -12129,7 +12161,7 @@ class EngineSlots {
12129
12161
  let content;
12130
12162
  let writtenAt;
12131
12163
  try {
12132
- content = readFileSync7(lock, "utf8");
12164
+ content = readFileSync8(lock, "utf8");
12133
12165
  writtenAt = statSync6(lock).mtimeMs;
12134
12166
  } catch {
12135
12167
  return false;
@@ -12167,31 +12199,55 @@ function nameSlotTaker(by) {
12167
12199
  }
12168
12200
  function slotWaitSaid(waitedMs, count, directory, holders = []) {
12169
12201
  const slots = count === 1 ? `the one engine slot on this machine (${directory}) is in use` : `all ${String(count)} engine slots on this machine (${directory}) are in use`;
12202
+ const named = holdersNamed(count, holders);
12203
+ const waiting = `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine: ${slots}`;
12204
+ if (named.length === 0) {
12205
+ return `${waiting}, by a gate or another tool, and the engine starts when one is given back.`;
12206
+ }
12207
+ if (count === 1) {
12208
+ return `${waiting}, by ${named.join(", ")}, and the engine starts when it is given back.`;
12209
+ }
12210
+ return `${waiting}: ${listed(named)}. The engine starts when one is given back.`;
12211
+ }
12212
+ function slotWaitedSaid(waitedMs, count, directory, holders) {
12213
+ const waited = `This call waited ${String(Math.round(waitedMs / 1000))} s for an engine slot in all.`;
12214
+ const slots = count === 1 ? `the one engine slot on this machine (${directory}) was in use` : `all ${String(count)} engine slots on this machine (${directory}) were in use`;
12215
+ const named = holdersNamed(count, holders);
12216
+ if (named.length === 0) {
12217
+ return `${waited} When it first waited, ${slots}, by a gate or another tool.`;
12218
+ }
12219
+ return count === 1 ? `${waited} When it first waited, ${slots}, by ${named.join(", ")}.` : `${waited} When it first waited, ${slots}: ${listed(named)}.`;
12220
+ }
12221
+ function holdersNamed(count, holders) {
12170
12222
  const held = new Map;
12171
12223
  for (const holder of holders) {
12172
12224
  const known = held.get(holder.pid);
12173
12225
  held.set(holder.pid, { by: known?.by ?? holder.by, slots: (known?.slots ?? 0) + 1 });
12174
12226
  }
12175
- const named = [...held].map(([pid, { by, slots: taken }]) => {
12227
+ return [...held].map(([pid, { by, slots: taken }]) => {
12176
12228
  const who = pid === process7.pid ? `this process (pid ${String(pid)})` : by === undefined ? `pid ${String(pid)}` : `${by} (pid ${String(pid)})`;
12177
12229
  return count === 1 ? who : `${String(taken)} by ${who}`;
12178
12230
  });
12179
- const waiting = `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine: ${slots}`;
12180
- if (named.length === 0) {
12181
- return `${waiting}, by a gate or another tool, and the engine starts when one is given back.`;
12182
- }
12183
- if (count === 1) {
12184
- return `${waiting}, by ${named.join(", ")}, and the engine starts when it is given back.`;
12185
- }
12186
- const list = named.length === 1 ? named.join("") : `${named.slice(0, -1).join(", ")} and ${named.at(-1) ?? ""}`;
12187
- return `${waiting}: ${list}. The engine starts when one is given back.`;
12231
+ }
12232
+ function listed(named) {
12233
+ return named.length === 1 ? named.join("") : `${named.slice(0, -1).join(", ")} and ${named.at(-1) ?? ""}`;
12188
12234
  }
12189
12235
  async function slotForCall() {
12190
12236
  const slots = await processSlots();
12191
12237
  const progress = callProgress();
12238
+ const waits = callSlotWaits();
12239
+ const began = Date.now();
12240
+ let blockedBy;
12192
12241
  const lock = await slots.take(callSignal(), progress === undefined ? undefined : (waitedMs) => {
12193
12242
  progress(waitedMs / 1000, slotWaitSaid(waitedMs, slots.count, slots.directory, slots.holders()));
12243
+ }, () => {
12244
+ blockedBy = slots.holders();
12194
12245
  });
12246
+ if (waits !== undefined && blockedBy !== undefined) {
12247
+ waits.ms += Date.now() - began;
12248
+ const heldBy = blockedBy;
12249
+ waits.said ??= (waitedMs) => slotWaitedSaid(waitedMs, slots.count, slots.directory, heldBy);
12250
+ }
12195
12251
  return {
12196
12252
  release: () => {
12197
12253
  slots.release(lock);
@@ -12209,6 +12265,7 @@ async function inASlot(body) {
12209
12265
  var SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 2, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", waitsMarked = 0, ours, saidTo, takenBy;
12210
12266
  var init_engine_slots = __esm(() => {
12211
12267
  init_alive();
12268
+ init_cache_home();
12212
12269
  init_call_signal();
12213
12270
  init_engine_launch();
12214
12271
  init_process_children();
@@ -12217,11 +12274,11 @@ var init_engine_slots = __esm(() => {
12217
12274
 
12218
12275
  // src/detection.ts
12219
12276
  import { existsSync as existsSync6, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
12220
- import { homedir as homedir3 } from "node:os";
12221
- import { join as join9 } from "node:path";
12277
+ import { homedir as homedir4 } from "node:os";
12278
+ import { join as join10 } from "node:path";
12222
12279
  function resolveHomeDirectory() {
12223
12280
  try {
12224
- return homedir3();
12281
+ return homedir4();
12225
12282
  } catch {
12226
12283
  return "";
12227
12284
  }
@@ -12242,7 +12299,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
12242
12299
  if (!pattern.test(name)) {
12243
12300
  continue;
12244
12301
  }
12245
- const fullPath = join9(directory, name);
12302
+ const fullPath = join10(directory, name);
12246
12303
  try {
12247
12304
  const stat = statSync7(fullPath);
12248
12305
  if (stat.isFile()) {
@@ -12251,7 +12308,7 @@ function scanDirectoryForGodotBinaries(directory, platform) {
12251
12308
  } catch {}
12252
12309
  }
12253
12310
  matches.sort((a, b) => b.mtime - a.mtime);
12254
- return matches.map((m) => join9(directory, m.name));
12311
+ return matches.map((m) => join10(directory, m.name));
12255
12312
  }
12256
12313
  function conventionalPaths(platform, home) {
12257
12314
  const paths = ["godot"];
@@ -12906,26 +12963,26 @@ var init_engine_run = __esm(() => {
12906
12963
 
12907
12964
  // src/doc-cache.ts
12908
12965
  import { existsSync as existsSync8, readdirSync as readdirSync5, statSync as statSync8 } from "node:fs";
12909
- import { homedir as homedir4 } from "node:os";
12910
- import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute5, join as join10 } from "node:path";
12966
+ import { homedir as homedir5 } from "node:os";
12967
+ import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute5, join as join11 } from "node:path";
12911
12968
  import process9 from "node:process";
12912
- function editorCacheDirectory(godotPath, platform = process9.platform, env = process9.env, home = homedir4()) {
12969
+ function editorCacheDirectory(godotPath, platform = process9.platform, env = process9.env, home = homedir5()) {
12913
12970
  const beside = dirname5(godotPath);
12914
12971
  const bundle = platform === "darwin" && basename4(beside) === "MacOS" && basename4(dirname5(beside)) === "Contents" ? dirname5(dirname5(dirname5(beside))) : null;
12915
12972
  for (const folder of bundle === null ? [beside] : [beside, bundle]) {
12916
- if (existsSync8(join10(folder, "._sc_")) || existsSync8(join10(folder, "_sc_"))) {
12917
- return join10(folder, "editor_data", "cache");
12973
+ if (existsSync8(join11(folder, "._sc_")) || existsSync8(join11(folder, "_sc_"))) {
12974
+ return join11(folder, "editor_data", "cache");
12918
12975
  }
12919
12976
  }
12920
12977
  if (platform === "win32") {
12921
12978
  const set = (name) => env[name] === "" ? undefined : env[name];
12922
- return join10(set("LOCALAPPDATA") ?? set("TEMP") ?? join10(home, "AppData", "Local"), "Godot");
12979
+ return join11(set("LOCALAPPDATA") ?? set("TEMP") ?? join11(home, "AppData", "Local"), "Godot");
12923
12980
  }
12924
12981
  if (platform === "darwin") {
12925
- return join10(home, "Library", "Caches", "Godot");
12982
+ return join11(home, "Library", "Caches", "Godot");
12926
12983
  }
12927
12984
  const xdg = env["XDG_CACHE_HOME"];
12928
- return join10(xdg !== undefined && isAbsolute5(xdg) ? xdg : join10(home, ".cache"), "godot");
12985
+ return join11(xdg !== undefined && isAbsolute5(xdg) ? xdg : join11(home, ".cache"), "godot");
12929
12986
  }
12930
12987
  function docCachesWritten(directory) {
12931
12988
  const written = new Map;
@@ -12937,7 +12994,7 @@ function docCachesWritten(directory) {
12937
12994
  }
12938
12995
  for (const name of names.filter((one) => CACHE_FILE.test(one))) {
12939
12996
  try {
12940
- written.set(join10(directory, name), statSync8(join10(directory, name)).mtimeMs);
12997
+ written.set(join11(directory, name), statSync8(join11(directory, name)).mtimeMs);
12941
12998
  } catch {}
12942
12999
  }
12943
13000
  return written;
@@ -12961,8 +13018,8 @@ var init_doc_cache = __esm(() => {
12961
13018
  });
12962
13019
 
12963
13020
  // src/project-scan.ts
12964
- import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync9 } from "node:fs";
12965
- import { basename as basename5, extname, join as join11, relative as relative4, sep as sep3 } from "node:path";
13021
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync9 } from "node:fs";
13022
+ import { basename as basename5, extname, join as join12, relative as relative4, sep as sep3 } from "node:path";
12966
13023
  function skipped(name) {
12967
13024
  return name.startsWith(".") || SKIPPED.has(name);
12968
13025
  }
@@ -12970,10 +13027,10 @@ function steppedOver(directory) {
12970
13027
  return gdignored(directory) || installedByUs(directory);
12971
13028
  }
12972
13029
  function gdignored(directory) {
12973
- return existsSync9(join11(directory, ".gdignore"));
13030
+ return existsSync9(join12(directory, ".gdignore"));
12974
13031
  }
12975
13032
  function installedByUs(directory) {
12976
- return existsSync9(join11(directory, VERSION_MARKER));
13033
+ return existsSync9(join12(directory, VERSION_MARKER));
12977
13034
  }
12978
13035
  function projectStructure(projectPath) {
12979
13036
  const structure = { scenes: 0, scripts: 0, assets: 0, other: 0 };
@@ -12986,7 +13043,7 @@ function projectStructure(projectPath) {
12986
13043
  continue;
12987
13044
  }
12988
13045
  if (entry.isDirectory()) {
12989
- visit(join11(directory, entry.name));
13046
+ visit(join12(directory, entry.name));
12990
13047
  } else if (entry.isFile()) {
12991
13048
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
12992
13049
  if (SIDECAR_EXTENSIONS.has(extension)) {
@@ -13016,7 +13073,7 @@ function folderFiles(directory, extensions) {
13016
13073
  function underGdignore(projectPath, relative) {
13017
13074
  const folders = relative.split("/").slice(0, -1);
13018
13075
  for (let depth = 1;depth <= folders.length; depth += 1) {
13019
- if (gdignored(join11(projectPath, ...folders.slice(0, depth)))) {
13076
+ if (gdignored(join12(projectPath, ...folders.slice(0, depth)))) {
13020
13077
  return true;
13021
13078
  }
13022
13079
  }
@@ -13033,7 +13090,7 @@ function steppedOverIn(directory) {
13033
13090
  continue;
13034
13091
  }
13035
13092
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
13036
- const path = join11(at, entry.name);
13093
+ const path = join12(at, entry.name);
13037
13094
  if (gdignored(path)) {
13038
13095
  found.push({ folder: spelled, why: "gdignore" });
13039
13096
  } else if (installedByUs(path)) {
@@ -13054,7 +13111,7 @@ function filesBelow(root, extensions) {
13054
13111
  continue;
13055
13112
  }
13056
13113
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
13057
- const path = join11(directory, entry.name);
13114
+ const path = join12(directory, entry.name);
13058
13115
  if (entry.isDirectory()) {
13059
13116
  if (!steppedOver(path)) {
13060
13117
  visit(path, spelled);
@@ -13079,14 +13136,14 @@ function scriptsWithoutUid(projectPath) {
13079
13136
  }
13080
13137
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
13081
13138
  if (entry.isDirectory()) {
13082
- visit(join11(directory, entry.name), spelled);
13139
+ visit(join12(directory, entry.name), spelled);
13083
13140
  continue;
13084
13141
  }
13085
13142
  if (!entry.isFile()) {
13086
13143
  continue;
13087
13144
  }
13088
13145
  const extension = entry.name.split(".").pop()?.toLowerCase() ?? "";
13089
- if (CARRIES_A_UID.has(extension) && !existsSync9(join11(directory, `${entry.name}.uid`))) {
13146
+ if (CARRIES_A_UID.has(extension) && !existsSync9(join12(directory, `${entry.name}.uid`))) {
13090
13147
  found.push(spelled);
13091
13148
  }
13092
13149
  }
@@ -13097,7 +13154,7 @@ function scriptsWithoutUid(projectPath) {
13097
13154
  function pathsInUidCache(projectPath) {
13098
13155
  let data;
13099
13156
  try {
13100
- data = readFileSync8(join11(projectPath, ".godot", "uid_cache.bin"));
13157
+ data = readFileSync9(join12(projectPath, ".godot", "uid_cache.bin"));
13101
13158
  } catch {
13102
13159
  return null;
13103
13160
  }
@@ -13125,7 +13182,7 @@ function pathsInUidCache(projectPath) {
13125
13182
  function steppedOverAround(projectPath, place) {
13126
13183
  let at = projectPath;
13127
13184
  for (const step of relative4(projectPath, place).split(sep3).filter((part) => part !== "")) {
13128
- at = join11(at, step);
13185
+ at = join12(at, step);
13129
13186
  if (!statSync9(at).isDirectory()) {
13130
13187
  break;
13131
13188
  }
@@ -13149,7 +13206,7 @@ function projectTexts(projectPath) {
13149
13206
  if (skipped(entry.name)) {
13150
13207
  continue;
13151
13208
  }
13152
- const absolute = join11(directory, entry.name);
13209
+ const absolute = join12(directory, entry.name);
13153
13210
  const spelled = `${prefix}${entry.name}`;
13154
13211
  if (entry.isDirectory()) {
13155
13212
  visit(absolute, `${spelled}/`);
@@ -13159,7 +13216,7 @@ function projectTexts(projectPath) {
13159
13216
  if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
13160
13217
  continue;
13161
13218
  }
13162
- const bytes = readFileSync8(absolute);
13219
+ const bytes = readFileSync9(absolute);
13163
13220
  if (!isBinary(bytes)) {
13164
13221
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
13165
13222
  }
@@ -13179,7 +13236,7 @@ function ignoredTexts(projectPath) {
13179
13236
  if (skipped(entry.name)) {
13180
13237
  continue;
13181
13238
  }
13182
- const absolute = join11(directory, entry.name);
13239
+ const absolute = join12(directory, entry.name);
13183
13240
  const spelled = `${prefix}${entry.name}`;
13184
13241
  if (entry.isDirectory()) {
13185
13242
  visit(absolute, `${spelled}/`, under);
@@ -13189,7 +13246,7 @@ function ignoredTexts(projectPath) {
13189
13246
  if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
13190
13247
  continue;
13191
13248
  }
13192
- const bytes = readFileSync8(absolute);
13249
+ const bytes = readFileSync9(absolute);
13193
13250
  if (!isBinary(bytes)) {
13194
13251
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
13195
13252
  }
@@ -13226,7 +13283,7 @@ function searchProject(projectPath, options) {
13226
13283
  };
13227
13284
  const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
13228
13285
  const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
13229
- const full = () => result.summary.truncated;
13286
+ let shown = 0;
13230
13287
  const notSearched = [];
13231
13288
  const met = new Set;
13232
13289
  const searchFile = (entryPath, name) => {
@@ -13237,28 +13294,33 @@ function searchProject(projectPath, options) {
13237
13294
  if (extensions !== null && !extensions.has(extension)) {
13238
13295
  return;
13239
13296
  }
13240
- const bytes = readFileSync8(entryPath);
13297
+ const bytes = readFileSync9(entryPath);
13241
13298
  if (extensions === null && isBinary(bytes)) {
13242
13299
  return;
13243
13300
  }
13244
13301
  result.summary.files_searched += 1;
13245
13302
  const matches = [];
13303
+ let matched = false;
13246
13304
  for (const [index, line] of bytes.toString("utf8").split(/\r?\n/).entries()) {
13247
13305
  const found = regex ? regex.exec(line) : plainMatch(line, needle, options.caseSensitive);
13248
13306
  if (found === null) {
13249
13307
  continue;
13250
13308
  }
13251
- if (result.summary.total_matches >= options.maxResults) {
13309
+ matched = true;
13310
+ result.summary.total_matches += 1;
13311
+ if (shown >= options.maxResults) {
13252
13312
  result.summary.truncated = true;
13253
- break;
13313
+ continue;
13254
13314
  }
13315
+ shown += 1;
13255
13316
  matches.push({ line: index + 1, content: line.trim(), match: found[0] });
13256
- result.summary.total_matches += 1;
13317
+ }
13318
+ if (matched) {
13319
+ result.summary.files_with_matches += 1;
13257
13320
  }
13258
13321
  if (matches.length > 0) {
13259
13322
  const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
13260
13323
  result.results.push({ file: `res://${relativePath}`, matches });
13261
- result.summary.files_with_matches += 1;
13262
13324
  }
13263
13325
  };
13264
13326
  const visit = (directory, named = false) => {
@@ -13269,13 +13331,10 @@ function searchProject(projectPath, options) {
13269
13331
  return;
13270
13332
  }
13271
13333
  for (const entry of readdirSync6(directory, { withFileTypes: true })) {
13272
- if (full()) {
13273
- return;
13274
- }
13275
13334
  if (skipped(entry.name)) {
13276
13335
  continue;
13277
13336
  }
13278
- const entryPath = join11(directory, entry.name);
13337
+ const entryPath = join12(directory, entry.name);
13279
13338
  if (entry.isDirectory()) {
13280
13339
  visit(entryPath);
13281
13340
  } else if (entry.isFile()) {
@@ -13351,8 +13410,8 @@ var init_project_scan = __esm(() => {
13351
13410
 
13352
13411
  // src/import-freshness.ts
13353
13412
  import { createHash as createHash2 } from "node:crypto";
13354
- import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync10 } from "node:fs";
13355
- import { join as join12 } from "node:path";
13413
+ import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync10 } from "node:fs";
13414
+ import { join as join13 } from "node:path";
13356
13415
  function md5Of(file) {
13357
13416
  let stats;
13358
13417
  try {
@@ -13366,7 +13425,7 @@ function md5Of(file) {
13366
13425
  }
13367
13426
  let md5;
13368
13427
  try {
13369
- md5 = createHash2("md5").update(readFileSync9(file)).digest("hex");
13428
+ md5 = createHash2("md5").update(readFileSync10(file)).digest("hex");
13370
13429
  } catch {
13371
13430
  return null;
13372
13431
  }
@@ -13392,7 +13451,7 @@ function sectionOf(text, section) {
13392
13451
  function unquoted(value) {
13393
13452
  return value === undefined ? "" : value.replace(/^"(.*)"$/, "$1");
13394
13453
  }
13395
- function listed(value) {
13454
+ function listed2(value) {
13396
13455
  return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
13397
13456
  }
13398
13457
  function importedIntoTheProject(projectPath, resPaths) {
@@ -13400,11 +13459,11 @@ function importedIntoTheProject(projectPath, resPaths) {
13400
13459
  for (const resPath of resPaths) {
13401
13460
  let sidecar;
13402
13461
  try {
13403
- sidecar = readFileSync9(`${onDisk(projectPath, resPath)}.import`, "utf8");
13462
+ sidecar = readFileSync10(`${onDisk(projectPath, resPath)}.import`, "utf8");
13404
13463
  } catch {
13405
13464
  continue;
13406
13465
  }
13407
- const outputs = listed(sectionOf(sidecar, "deps").get("dest_files")).filter((output) => output.startsWith("res://") && !output.startsWith("res://.godot/"));
13466
+ const outputs = listed2(sectionOf(sidecar, "deps").get("dest_files")).filter((output) => output.startsWith("res://") && !output.startsWith("res://.godot/"));
13408
13467
  if (outputs.length > 0) {
13409
13468
  wrote[resPath] = outputs;
13410
13469
  }
@@ -13412,7 +13471,7 @@ function importedIntoTheProject(projectPath, resPaths) {
13412
13471
  return wrote;
13413
13472
  }
13414
13473
  function onDisk(projectPath, resPath) {
13415
- return join12(projectPath, resPath.slice("res://".length));
13474
+ return join13(projectPath, resPath.slice("res://".length));
13416
13475
  }
13417
13476
  function behind(projectPath, resPath, sidecar) {
13418
13477
  const remap = sectionOf(sidecar, "remap");
@@ -13420,15 +13479,15 @@ function behind(projectPath, resPath, sidecar) {
13420
13479
  if (importer === "keep" || importer === "skip") {
13421
13480
  return null;
13422
13481
  }
13423
- const outputs = listed(sectionOf(sidecar, "deps").get("dest_files"));
13482
+ const outputs = listed2(sectionOf(sidecar, "deps").get("dest_files"));
13424
13483
  if (outputs.some((output) => !existsSync10(onDisk(projectPath, output)))) {
13425
13484
  return "its imported output is not on disk";
13426
13485
  }
13427
13486
  const name = resPath.slice(resPath.lastIndexOf("/") + 1);
13428
- const record = join12(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
13487
+ const record = join13(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
13429
13488
  let recorded = "";
13430
13489
  try {
13431
- recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync9(record, "utf8"))?.[1] ?? "";
13490
+ recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync10(record, "utf8"))?.[1] ?? "";
13432
13491
  } catch {
13433
13492
  recorded = "";
13434
13493
  }
@@ -13441,7 +13500,7 @@ function behind(projectPath, resPath, sidecar) {
13441
13500
  function staleImports(projectPath) {
13442
13501
  const stale = [];
13443
13502
  const visit = (directory, resDirectory) => {
13444
- if (existsSync10(join12(directory, ".gdignore")) || existsSync10(join12(directory, VERSION_MARKER))) {
13503
+ if (existsSync10(join13(directory, ".gdignore")) || existsSync10(join13(directory, VERSION_MARKER))) {
13445
13504
  return;
13446
13505
  }
13447
13506
  let entries;
@@ -13457,7 +13516,7 @@ function staleImports(projectPath) {
13457
13516
  }
13458
13517
  const resPath = `${resDirectory}${entry.name}`;
13459
13518
  if (entry.isDirectory()) {
13460
- visit(join12(directory, entry.name), `${resPath}/`);
13519
+ visit(join13(directory, entry.name), `${resPath}/`);
13461
13520
  continue;
13462
13521
  }
13463
13522
  if (!entry.isFile()) {
@@ -13470,7 +13529,7 @@ function staleImports(projectPath) {
13470
13529
  }
13471
13530
  let sidecar;
13472
13531
  try {
13473
- sidecar = readFileSync9(join12(directory, entry.name), "utf8");
13532
+ sidecar = readFileSync10(join13(directory, entry.name), "utf8");
13474
13533
  } catch {
13475
13534
  continue;
13476
13535
  }
@@ -13534,12 +13593,12 @@ import {
13534
13593
  existsSync as existsSync11,
13535
13594
  mkdirSync as mkdirSync4,
13536
13595
  readdirSync as readdirSync8,
13537
- readFileSync as readFileSync10,
13596
+ readFileSync as readFileSync11,
13538
13597
  renameSync as renameSync2,
13539
13598
  unlinkSync,
13540
13599
  writeFileSync as writeFileSync6
13541
13600
  } from "node:fs";
13542
- import { basename as basename6, dirname as dirname6, isAbsolute as isAbsolute6, join as join13 } from "node:path";
13601
+ import { basename as basename6, dirname as dirname6, isAbsolute as isAbsolute6, join as join14 } from "node:path";
13543
13602
  function freeLibraryCopies(projectPath, platform = process.platform) {
13544
13603
  const setAside = [];
13545
13604
  const swept = [];
@@ -13549,10 +13608,10 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
13549
13608
  for (const library of windowsLibraries(projectPath)) {
13550
13609
  const folder = dirname6(library);
13551
13610
  const copy = `~${basename6(library)}`;
13552
- if (existsSync11(join13(folder, copy))) {
13553
- const aside = join13(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
13611
+ if (existsSync11(join14(folder, copy))) {
13612
+ const aside = join14(folder, `${copy}.aside-${process.pid}-${Date.now()}`);
13554
13613
  try {
13555
- renameSync2(join13(folder, copy), aside);
13614
+ renameSync2(join14(folder, copy), aside);
13556
13615
  setAside.push(aside);
13557
13616
  } catch {}
13558
13617
  }
@@ -13565,8 +13624,8 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
13565
13624
  for (const name of names) {
13566
13625
  if (name.length > copy.length && name.toLowerCase().startsWith(copy.toLowerCase())) {
13567
13626
  try {
13568
- unlinkSync(join13(folder, name));
13569
- swept.push(join13(folder, name));
13627
+ unlinkSync(join14(folder, name));
13628
+ swept.push(join14(folder, name));
13570
13629
  } catch {}
13571
13630
  }
13572
13631
  }
@@ -13574,13 +13633,13 @@ function freeLibraryCopies(projectPath, platform = process.platform) {
13574
13633
  return { setAside, swept };
13575
13634
  }
13576
13635
  function seedExtensionList(projectPath) {
13577
- const list = join13(projectPath, ".godot", "extension_list.cfg");
13636
+ const list = join14(projectPath, ".godot", "extension_list.cfg");
13578
13637
  if (existsSync11(list)) {
13579
13638
  return null;
13580
13639
  }
13581
13640
  const found = [];
13582
13641
  const visit = (directory, resDirectory) => {
13583
- if (existsSync11(join13(directory, ".gdignore"))) {
13642
+ if (existsSync11(join14(directory, ".gdignore"))) {
13584
13643
  return;
13585
13644
  }
13586
13645
  let entries;
@@ -13594,7 +13653,7 @@ function seedExtensionList(projectPath) {
13594
13653
  continue;
13595
13654
  }
13596
13655
  if (entry.isDirectory()) {
13597
- visit(join13(directory, entry.name), `${resDirectory}${entry.name}/`);
13656
+ visit(join14(directory, entry.name), `${resDirectory}${entry.name}/`);
13598
13657
  } else if (entry.name.endsWith(".gdextension")) {
13599
13658
  found.push(`${resDirectory}${entry.name}`);
13600
13659
  }
@@ -13612,7 +13671,7 @@ function seedExtensionList(projectPath) {
13612
13671
  return found;
13613
13672
  }
13614
13673
  function windowsLibraries(projectPath) {
13615
- const listed = readOrNull(join13(projectPath, ".godot", "extension_list.cfg"));
13674
+ const listed = readOrNull(join14(projectPath, ".godot", "extension_list.cfg"));
13616
13675
  if (listed === null) {
13617
13676
  return [];
13618
13677
  }
@@ -13644,13 +13703,13 @@ function resolveResource(projectPath, relativeTo, path) {
13644
13703
  return null;
13645
13704
  }
13646
13705
  if (path.startsWith("res://")) {
13647
- return join13(projectPath, path.slice("res://".length));
13706
+ return join14(projectPath, path.slice("res://".length));
13648
13707
  }
13649
- return isAbsolute6(path) ? path : join13(relativeTo, path);
13708
+ return isAbsolute6(path) ? path : join14(relativeTo, path);
13650
13709
  }
13651
13710
  function readOrNull(path) {
13652
13711
  try {
13653
- return readFileSync10(path, "utf8");
13712
+ return readFileSync11(path, "utf8");
13654
13713
  } catch {
13655
13714
  return null;
13656
13715
  }
@@ -13659,32 +13718,32 @@ var init_library_copies = () => {};
13659
13718
 
13660
13719
  // src/reimport.ts
13661
13720
  import { createHash as createHash3 } from "node:crypto";
13662
- import { existsSync as existsSync12, readFileSync as readFileSync11, rmSync as rmSync7, statSync as statSync11, utimesSync, writeFileSync as writeFileSync7 } from "node:fs";
13663
- import { basename as basename7, join as join14 } from "node:path";
13721
+ import { existsSync as existsSync12, readFileSync as readFileSync12, rmSync as rmSync7, statSync as statSync11, utimesSync, writeFileSync as writeFileSync7 } from "node:fs";
13722
+ import { basename as basename7, join as join15 } from "node:path";
13664
13723
  function forceNextImport(projectPath, resourcePath) {
13665
13724
  const relative = resourcePath.replace(/^res:\/\//, "");
13666
- const sidecar = join14(projectPath, `${relative}.import`);
13725
+ const sidecar = join15(projectPath, `${relative}.import`);
13667
13726
  if (!existsSync12(sidecar)) {
13668
13727
  return;
13669
13728
  }
13670
13729
  const { atime, mtimeMs } = statSync11(sidecar);
13671
- writeFileSync7(sidecar, readFileSync11(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
13730
+ writeFileSync7(sidecar, readFileSync12(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
13672
13731
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
13673
13732
  rmSync7(importRecord(projectPath, resourcePath), { force: true });
13674
13733
  }
13675
13734
  function importRecord(projectPath, resourcePath) {
13676
13735
  const digest = createHash3("md5").update(resourcePath).digest("hex");
13677
- return join14(projectPath, ".godot", "imported", `${basename7(resourcePath)}-${digest}.md5`);
13736
+ return join15(projectPath, ".godot", "imported", `${basename7(resourcePath)}-${digest}.md5`);
13678
13737
  }
13679
13738
  var init_reimport = () => {};
13680
13739
 
13681
13740
  // src/scratch.ts
13682
13741
  import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readdirSync as readdirSync9, rmSync as rmSync8 } from "node:fs";
13683
13742
  import { tmpdir as tmpdir3 } from "node:os";
13684
- import { join as join15 } from "node:path";
13743
+ import { join as join16 } from "node:path";
13685
13744
  import process10 from "node:process";
13686
13745
  function scratchDirectory(kind) {
13687
- return mkdtempSync2(join15(tmpdir3(), `gdharness-${kind}-${process10.pid}-`));
13746
+ return mkdtempSync2(join16(tmpdir3(), `gdharness-${kind}-${process10.pid}-`));
13688
13747
  }
13689
13748
  function sweepAbandonedScratch(directory = tmpdir3()) {
13690
13749
  let entries;
@@ -13700,7 +13759,7 @@ function sweepAbandonedScratch(directory = tmpdir3()) {
13700
13759
  continue;
13701
13760
  }
13702
13761
  try {
13703
- remove(join15(directory, entry));
13762
+ remove(join16(directory, entry));
13704
13763
  } catch {}
13705
13764
  }
13706
13765
  }
@@ -13786,7 +13845,7 @@ var init_scratch = __esm(() => {
13786
13845
  });
13787
13846
 
13788
13847
  // src/script-origins.ts
13789
- import { join as join16 } from "node:path";
13848
+ import { join as join17 } from "node:path";
13790
13849
  function unresolvedClassIn(message) {
13791
13850
  return UNRESOLVED.exec(message)?.[1] ?? null;
13792
13851
  }
@@ -13848,7 +13907,7 @@ function checkWithEngine(godotPath, projectPath) {
13848
13907
  const ran = await runEngine(godotPath, [
13849
13908
  "--headless",
13850
13909
  "--log-file",
13851
- join16(logDir, "engine.log"),
13910
+ join17(logDir, "engine.log"),
13852
13911
  "--path",
13853
13912
  projectPath,
13854
13913
  "--check-only",
@@ -13894,8 +13953,8 @@ var init_script_origins = __esm(() => {
13894
13953
  });
13895
13954
 
13896
13955
  // src/headless.ts
13897
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
13898
- import { dirname as dirname7, join as join17 } from "node:path";
13956
+ import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
13957
+ import { dirname as dirname7, join as join18 } from "node:path";
13899
13958
  function snakeCased(params) {
13900
13959
  const result = emptyRecord();
13901
13960
  for (const [key, value] of Object.entries(params)) {
@@ -14051,7 +14110,7 @@ async function importOnce(godotPath, projectPath) {
14051
14110
  ran = await runEngine(godotPath, [
14052
14111
  "--headless",
14053
14112
  "--log-file",
14054
- join17(logDir, "engine.log"),
14113
+ join18(logDir, "engine.log"),
14055
14114
  "--path",
14056
14115
  projectPath,
14057
14116
  "--import"
@@ -14087,14 +14146,14 @@ async function runOperation(engine, operation, params, projectPath) {
14087
14146
  }
14088
14147
  async function operationOutcome(engine, operation, params, projectPath) {
14089
14148
  const paramsDir = scratchDirectory("params");
14090
- const paramsFile = join17(paramsDir, `${operation}.json`);
14091
- const answerFile = join17(paramsDir, "answer.json");
14149
+ const paramsFile = join18(paramsDir, `${operation}.json`);
14150
+ const answerFile = join18(paramsDir, "answer.json");
14092
14151
  writeFileSync8(paramsFile, JSON.stringify(snakeCased(params)), "utf8");
14093
14152
  const args = [
14094
14153
  "--headless",
14095
14154
  ...engine.editor === true ? ["--editor"] : [],
14096
14155
  "--log-file",
14097
- join17(paramsDir, "engine.log"),
14156
+ join18(paramsDir, "engine.log"),
14098
14157
  "--path",
14099
14158
  projectPath,
14100
14159
  "--script",
@@ -14185,7 +14244,7 @@ function answerIn(file) {
14185
14244
  if (!existsSync14(file)) {
14186
14245
  return { kind: "none" };
14187
14246
  }
14188
- const text = readFileSync12(file, "utf8");
14247
+ const text = readFileSync13(file, "utf8");
14189
14248
  try {
14190
14249
  const parsed = JSON.parse(text);
14191
14250
  return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? { kind: "answer", payload: parsed } : { kind: "unreadable", why: `it is JSON but not an object: ${text.slice(0, 200)}` };
@@ -14210,9 +14269,9 @@ var init_headless = __esm(() => {
14210
14269
  });
14211
14270
 
14212
14271
  // src/update-check.ts
14213
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
14214
- import { homedir as homedir5, tmpdir as tmpdir4 } from "node:os";
14215
- import { join as join18 } from "node:path";
14272
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
14273
+ import { homedir as homedir6, tmpdir as tmpdir4 } from "node:os";
14274
+ import { join as join19 } from "node:path";
14216
14275
  function packageUrl(root) {
14217
14276
  return `${root.replace(/\/+$/, "")}/gdharness/latest`;
14218
14277
  }
@@ -14232,22 +14291,22 @@ function cacheDirectory(environment) {
14232
14291
  const value = environment[name];
14233
14292
  return value !== undefined && value !== "" ? value : null;
14234
14293
  };
14235
- const home = set("HOME") ?? homedir5();
14294
+ const home = set("HOME") ?? homedir6();
14236
14295
  if (process.platform === "win32") {
14237
- return join18(set("LOCALAPPDATA") ?? home, "gdharness");
14296
+ return join19(set("LOCALAPPDATA") ?? home, "gdharness");
14238
14297
  }
14239
14298
  if (process.platform === "darwin") {
14240
- return join18(home, "Library", "Caches", "gdharness");
14299
+ return join19(home, "Library", "Caches", "gdharness");
14241
14300
  }
14242
- return join18(set("XDG_CACHE_HOME") ?? join18(home, ".cache"), "gdharness");
14301
+ return join19(set("XDG_CACHE_HOME") ?? join19(home, ".cache"), "gdharness");
14243
14302
  }
14244
14303
  function cacheFile(environment = process.env) {
14245
14304
  try {
14246
14305
  const directory = cacheDirectory(environment);
14247
14306
  mkdirSync5(directory, { recursive: true, mode: 448 });
14248
- return join18(directory, "update-check.json");
14307
+ return join19(directory, "update-check.json");
14249
14308
  } catch {
14250
- return join18(tmpdir4(), "gdharness-update-check.json");
14309
+ return join19(tmpdir4(), "gdharness-update-check.json");
14251
14310
  }
14252
14311
  }
14253
14312
  function readCache(path) {
@@ -14255,7 +14314,7 @@ function readCache(path) {
14255
14314
  if (!existsSync15(path)) {
14256
14315
  return null;
14257
14316
  }
14258
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
14317
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
14259
14318
  if (typeof parsed !== "object" || parsed === null) {
14260
14319
  return null;
14261
14320
  }
@@ -14403,7 +14462,7 @@ var init_update_check = __esm(() => {
14403
14462
  });
14404
14463
 
14405
14464
  // src/server-version.ts
14406
- import { readFileSync as readFileSync14 } from "node:fs";
14465
+ import { readFileSync as readFileSync15 } from "node:fs";
14407
14466
  function staleAddon(addonDigest, shippedDigest) {
14408
14467
  return addonDigest !== shippedDigest;
14409
14468
  }
@@ -14462,7 +14521,7 @@ var init_server_version = __esm(() => {
14462
14521
  DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
14463
14522
  SERVER_VERSION = (() => {
14464
14523
  try {
14465
- const pkg = JSON.parse(readFileSync14(new URL("../package.json", import.meta.url), "utf8"));
14524
+ const pkg = JSON.parse(readFileSync15(new URL("../package.json", import.meta.url), "utf8"));
14466
14525
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
14467
14526
  } catch {
14468
14527
  return "0.0.0";
@@ -14735,7 +14794,7 @@ var init_ports = __esm(() => {
14735
14794
  import { existsSync as existsSync16, realpathSync as realpathSync2 } from "node:fs";
14736
14795
  import { readFile, realpath } from "node:fs/promises";
14737
14796
  import { createConnection } from "node:net";
14738
- import { dirname as dirname8, join as join19, resolve as resolve6 } from "node:path";
14797
+ import { dirname as dirname8, join as join20, resolve as resolve6 } from "node:path";
14739
14798
  import { setTimeout as delay4 } from "node:timers/promises";
14740
14799
  import { fileURLToPath, pathToFileURL } from "node:url";
14741
14800
  function initializeTimeoutMs() {
@@ -15413,7 +15472,7 @@ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
15413
15472
  } catch {
15414
15473
  throw new ArgumentRefusal(`Project path does not exist: ${requestedProjectPath}`);
15415
15474
  }
15416
- if (!existsSync16(join19(projectPath, "project.godot"))) {
15475
+ if (!existsSync16(join20(projectPath, "project.godot"))) {
15417
15476
  throw new ArgumentRefusal(`Not a Godot project: ${projectPath}. Point projectPath at the directory holding project.godot.`);
15418
15477
  }
15419
15478
  const contained = resolveWithinProject(projectPath, scriptPathValue);
@@ -15573,7 +15632,7 @@ var init_lsp_client = __esm(() => {
15573
15632
  });
15574
15633
 
15575
15634
  // src/resources.ts
15576
- import { readFileSync as readFileSync16, realpathSync as realpathSync3 } from "node:fs";
15635
+ import { readFileSync as readFileSync17, realpathSync as realpathSync3 } from "node:fs";
15577
15636
  import { extname as extname2, resolve as resolve8 } from "node:path";
15578
15637
  function ensureProjectPath(getProjectPath) {
15579
15638
  const projectPath = getProjectPath();
@@ -15769,7 +15828,7 @@ function readResourceText(uri, getProjectPath) {
15769
15828
  const parsedUri = parseGodotUri(uri);
15770
15829
  if (parsedUri.kind === "project-info") {
15771
15830
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
15772
- const rawProject = readFileSync16(projectFilePath, "utf-8");
15831
+ const rawProject = readFileSync17(projectFilePath, "utf-8");
15773
15832
  const parsedProject = parseProjectGodot(rawProject);
15774
15833
  return {
15775
15834
  mimeType: "application/json",
@@ -15778,7 +15837,7 @@ function readResourceText(uri, getProjectPath) {
15778
15837
  }
15779
15838
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
15780
15839
  ensureAllowedExtension(parsedUri.kind, filePath);
15781
- const text = readFileSync16(filePath, "utf-8");
15840
+ const text = readFileSync17(filePath, "utf-8");
15782
15841
  return {
15783
15842
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
15784
15843
  text
@@ -15925,13 +15984,13 @@ import {
15925
15984
  existsSync as existsSync18,
15926
15985
  mkdirSync as mkdirSync6,
15927
15986
  readdirSync as readdirSync11,
15928
- readFileSync as readFileSync17,
15987
+ readFileSync as readFileSync18,
15929
15988
  rmdirSync,
15930
15989
  rmSync as rmSync9,
15931
15990
  statSync as statSync12,
15932
15991
  writeFileSync as writeFileSync10
15933
15992
  } from "node:fs";
15934
- import { dirname as dirname9, join as join21 } from "node:path";
15993
+ import { dirname as dirname9, join as join22 } from "node:path";
15935
15994
  import { fileURLToPath as fileURLToPath2 } from "node:url";
15936
15995
  function addonMarking(name) {
15937
15996
  if (name === RUNTIME_ADDON) {
@@ -15952,21 +16011,21 @@ function addonsDigest(names, from) {
15952
16011
  if (name === VERSION_MARKER || name === DIGEST_MARKER) {
15953
16012
  continue;
15954
16013
  }
15955
- const path = join21(directory, name);
16014
+ const path = join22(directory, name);
15956
16015
  const inside = `${relative}/${name}`;
15957
16016
  if (statSync12(path).isDirectory()) {
15958
16017
  walk(path, inside);
15959
16018
  } else {
15960
16019
  hash.update(`${inside}
15961
16020
  `);
15962
- hash.update(readFileSync17(path));
16021
+ hash.update(readFileSync18(path));
15963
16022
  hash.update(`
15964
16023
  `);
15965
16024
  }
15966
16025
  }
15967
16026
  };
15968
16027
  for (const name of names) {
15969
- walk(join21(from, name), name);
16028
+ walk(join22(from, name), name);
15970
16029
  }
15971
16030
  return hash.digest("hex");
15972
16031
  }
@@ -15985,28 +16044,28 @@ function installedRuntimeDigest(projectPath) {
15985
16044
  return digestMarker(projectPath, RUNTIME_ADDON);
15986
16045
  }
15987
16046
  function digestMarker(projectPath, addon) {
15988
- const marker = join21(projectPath, "addons", addon, DIGEST_MARKER);
16047
+ const marker = join22(projectPath, "addons", addon, DIGEST_MARKER);
15989
16048
  if (!existsSync18(marker)) {
15990
16049
  return null;
15991
16050
  }
15992
- const said = readFileSync17(marker, "utf8").trim();
16051
+ const said = readFileSync18(marker, "utf8").trim();
15993
16052
  return said === "" ? null : said;
15994
16053
  }
15995
16054
  function shippedAddonsDirectory() {
15996
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "addons");
16055
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "addons");
15997
16056
  }
15998
16057
  function shippedOperationsScript() {
15999
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "operations", "godot_operations.gd");
16058
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "operations", "godot_operations.gd");
16000
16059
  }
16001
16060
  function shippedLoader() {
16002
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "loader", "loader.gd");
16061
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "loader", "loader.gd");
16003
16062
  }
16004
16063
  function loaderState(projectPath) {
16005
- const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16064
+ const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16006
16065
  if (!existsSync18(copy)) {
16007
16066
  return "missing";
16008
16067
  }
16009
- const lines = (path) => readFileSync17(path, "utf8").replaceAll(`\r
16068
+ const lines = (path) => readFileSync18(path, "utf8").replaceAll(`\r
16010
16069
  `, `
16011
16070
  `);
16012
16071
  return lines(copy) === lines(shippedLoader()) ? "current" : "differs";
@@ -16015,13 +16074,13 @@ function writeLoader(projectPath) {
16015
16074
  if (loaderState(projectPath) === "current") {
16016
16075
  return false;
16017
16076
  }
16018
- const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16077
+ const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16019
16078
  mkdirSync6(dirname9(copy), { recursive: true });
16020
- writeFileSync10(copy, readFileSync17(shippedLoader()));
16079
+ writeFileSync10(copy, readFileSync18(shippedLoader()));
16021
16080
  return true;
16022
16081
  }
16023
16082
  function removeLoader(projectPath) {
16024
- const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16083
+ const copy = join22(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16025
16084
  if (!existsSync18(copy)) {
16026
16085
  return false;
16027
16086
  }
@@ -16034,14 +16093,14 @@ function removeLoader(projectPath) {
16034
16093
  return true;
16035
16094
  }
16036
16095
  function shippedListedRunner() {
16037
- return join21(dirname9(fileURLToPath2(import.meta.url)), "godot", "tests", "listed.gd");
16096
+ return join22(dirname9(fileURLToPath2(import.meta.url)), "godot", "tests", "listed.gd");
16038
16097
  }
16039
16098
  function gdUnit4Unusable(projectPath) {
16040
16099
  const marker = "addons/gdUnit4/plugin.cfg";
16041
- if (!existsSync18(join21(projectPath, ...marker.split("/")))) {
16100
+ if (!existsSync18(join22(projectPath, ...marker.split("/")))) {
16042
16101
  return { missing: true, reason: `gdUnit4 is not installed in this project: no ${marker}` };
16043
16102
  }
16044
- if (!existsSync18(join21(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
16103
+ if (!existsSync18(join22(projectPath, ...GDUNIT4_CI_RUNNER.split("/")))) {
16045
16104
  return {
16046
16105
  missing: false,
16047
16106
  reason: `gdUnit4 in this project is older than 6.0, which the shipped runner needs: no ${GDUNIT4_CI_RUNNER}`
@@ -16052,43 +16111,43 @@ function gdUnit4Unusable(projectPath) {
16052
16111
  function installAddons(projectPath, from = shippedAddonsDirectory()) {
16053
16112
  const installed = [];
16054
16113
  for (const name of ADDONS) {
16055
- const source = join21(from, name);
16056
- if (!existsSync18(join21(source, addonMarking(name)))) {
16114
+ const source = join22(from, name);
16115
+ if (!existsSync18(join22(source, addonMarking(name)))) {
16057
16116
  throw new Error(`The package holds no ${name} addon at ${source}.`);
16058
16117
  }
16059
- const target = join21(projectPath, "addons", name);
16118
+ const target = join22(projectPath, "addons", name);
16060
16119
  const replaced = existsSync18(target);
16061
16120
  rmSync9(target, { recursive: true, force: true });
16062
16121
  mkdirSync6(dirname9(target), { recursive: true });
16063
16122
  cpSync(source, target, { recursive: true });
16064
- writeFileSync10(join21(target, VERSION_MARKER), `${SERVER_VERSION}
16123
+ writeFileSync10(join22(target, VERSION_MARKER), `${SERVER_VERSION}
16065
16124
  `);
16066
16125
  installed.push({ name, path: target, replaced });
16067
16126
  }
16068
- writeFileSync10(join21(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
16127
+ writeFileSync10(join22(projectPath, "addons", ADDONS[0], DIGEST_MARKER), `${editorAddonDigest(from)}
16069
16128
  `);
16070
- writeFileSync10(join21(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
16129
+ writeFileSync10(join22(projectPath, "addons", RUNTIME_ADDON, DIGEST_MARKER), `${runtimeAddonDigest(from)}
16071
16130
  `);
16072
16131
  return installed;
16073
16132
  }
16074
16133
  function installedAddonVersion(projectPath) {
16075
- const marker = join21(projectPath, "addons", ADDONS[0], VERSION_MARKER);
16134
+ const marker = join22(projectPath, "addons", ADDONS[0], VERSION_MARKER);
16076
16135
  if (!existsSync18(marker)) {
16077
16136
  return null;
16078
16137
  }
16079
- const said = readFileSync17(marker, "utf8").trim();
16138
+ const said = readFileSync18(marker, "utf8").trim();
16080
16139
  return said === "" ? null : said;
16081
16140
  }
16082
16141
  function removeAddons(projectPath) {
16083
16142
  const removed = [];
16084
16143
  for (const name of ADDONS) {
16085
- const target = join21(projectPath, "addons", name);
16144
+ const target = join22(projectPath, "addons", name);
16086
16145
  if (existsSync18(target)) {
16087
16146
  rmSync9(target, { recursive: true, force: true });
16088
16147
  removed.push(target);
16089
16148
  }
16090
16149
  }
16091
- const addons = join21(projectPath, "addons");
16150
+ const addons = join22(projectPath, "addons");
16092
16151
  if (removed.length > 0 && existsSync18(addons) && readdirSync11(addons).length === 0) {
16093
16152
  rmdirSync(addons);
16094
16153
  removed.push(addons);
@@ -16131,7 +16190,7 @@ function loaderAmong(projectPath, named) {
16131
16190
  }
16132
16191
  let source = null;
16133
16192
  try {
16134
- source = readFileSync17(join21(projectPath, path), "utf8");
16193
+ source = readFileSync18(join22(projectPath, path), "utf8");
16135
16194
  } catch {}
16136
16195
  if (source?.includes(RUNTIME_SCRIPT)) {
16137
16196
  return { name: entry, path, exists: true };
@@ -16187,10 +16246,10 @@ function enabledPlugins(settings) {
16187
16246
  function inspectProject(projectPath) {
16188
16247
  const problems = [];
16189
16248
  const addons = ADDONS.map((name) => {
16190
- const directory = join21(projectPath, "addons", name);
16191
- const marker = join21(directory, VERSION_MARKER);
16249
+ const directory = join22(projectPath, "addons", name);
16250
+ const marker = join22(directory, VERSION_MARKER);
16192
16251
  const installed = existsSync18(directory);
16193
- const version = existsSync18(marker) ? readFileSync17(marker, "utf8").trim() : null;
16252
+ const version = existsSync18(marker) ? readFileSync18(marker, "utf8").trim() : null;
16194
16253
  const current = version === SERVER_VERSION;
16195
16254
  if (!installed) {
16196
16255
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -16199,7 +16258,7 @@ function inspectProject(projectPath) {
16199
16258
  }
16200
16259
  return { name, installed, version, current };
16201
16260
  });
16202
- const settings = parseProjectGodot(readFileSync17(join21(projectPath, "project.godot"), "utf8"));
16261
+ const settings = parseProjectGodot(readFileSync18(join22(projectPath, "project.godot"), "utf8"));
16203
16262
  const pluginsEnabled = enabledPlugins(settings);
16204
16263
  for (const name of EDITOR_PLUGINS) {
16205
16264
  if (!pluginsEnabled.includes(name)) {
@@ -16223,7 +16282,7 @@ function inspectProject(projectPath) {
16223
16282
  const ignoredAutoloads = [];
16224
16283
  const addonMissing = (path) => addons.some((addon) => !addon.installed && path.startsWith(`addons/${addon.name}/`));
16225
16284
  for (const [name, path] of named) {
16226
- if (!existsSync18(join21(projectPath, path))) {
16285
+ if (!existsSync18(join22(projectPath, path))) {
16227
16286
  if (!addonMissing(path)) {
16228
16287
  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`);
16229
16288
  }
@@ -16592,7 +16651,7 @@ var init_tool_definitions = __esm(() => {
16592
16651
  include: {
16593
16652
  type: "array",
16594
16653
  items: { type: "string", enum: [...PROJECT_INFO_SECTIONS] },
16595
- description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, each input action a script reads by a name written in the call that neither the project nor the engine defines, with every line reading it, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
16654
+ description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets (runnable marks the one preset of its platform the editor's one-click deploy uses), the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, each input action a script reads by a name written in the call that neither the project nor the engine defines, with every line reading it, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
16596
16655
  },
16597
16656
  preset: { type: "string", description: "For validation: the export preset to validate against." },
16598
16657
  detail: {
@@ -16623,14 +16682,18 @@ var init_tool_definitions = __esm(() => {
16623
16682
  prefix: {
16624
16683
  type: "string",
16625
16684
  ops: ["get"],
16626
- description: 'get: answer with every setting whose name starts with this, and the type the engine registers for each, instead of one setting by name. "debug/gdscript/warnings/" answers the whole family. The type matters: a family of levels can hold a setting that is a bool, and writing a level over it looks like it worked.'
16685
+ description: 'get: answer with every setting whose name starts with this, and the type the engine registers for each, a typed array or dictionary with what it holds as project.godot writes it (Dictionary[String, bool]), instead of one setting by name. "debug/gdscript/warnings/" answers the whole family. The type matters: a family of levels can hold a setting that is a bool, and writing a level over it looks like it worked.'
16627
16686
  },
16628
16687
  value: {
16629
16688
  blank: true,
16630
16689
  ops: ["set"],
16631
16690
  description: 'The value to write, fitted to the type the setting is registered as, the way scene_node set fits a property. Engine types may be written as their fields, {"x": 1, "y": 2}, or tagged, {"_type": "Vector2", "x": 1, "y": 2}, and infinity as {"_type": "float", "value": "inf"}, the form a read answers with; one carrying a field its type has not is refused naming it, and a colour may also be its html code, "#ff8000". "" writes an empty string. A setting the engine does not declare is written as given, a whole number as an int and a plain list as an Array, so a packed array is tagged with its type, {"_type": "PackedStringArray", "value": ["res://a/"]}; a tag that cannot be read as its type is refused rather than written as a dictionary. When a setting in its section is named like an undeclared one, note names the nearest, as it may be a misspelt one. Every line of project.godot for another setting stays as it was, one holding a default value included.'
16632
16691
  },
16633
- name: { type: "string", ops: ["add_autoload", "remove_autoload"], description: "Autoload name." },
16692
+ name: {
16693
+ type: "string",
16694
+ ops: ["add_autoload", "remove_autoload"],
16695
+ description: "Autoload name. add_autoload on a name already registered replaces its entry, as the editor's Add does, and answers action updated with old_path, the path it held, which no longer loads."
16696
+ },
16634
16697
  path: {
16635
16698
  type: "string",
16636
16699
  ops: ["add_autoload"],
@@ -16784,7 +16847,7 @@ var init_tool_definitions = __esm(() => {
16784
16847
  maxResults: {
16785
16848
  type: "integer",
16786
16849
  minimum: 1,
16787
- description: "How many matching lines to answer. Default 100."
16850
+ description: "How many matching lines to answer. Default 100. Past it truncated is true, and the summary still counts the whole search."
16788
16851
  }
16789
16852
  },
16790
16853
  requires: ["projectPath", "query"]
@@ -16800,12 +16863,12 @@ var init_tool_definitions = __esm(() => {
16800
16863
  direction: {
16801
16864
  type: "string",
16802
16865
  enum: ["forward", "reverse"],
16803
- description: "forward: what this resource loads, with each reference closing a cycle marked circular and the cycle under circular_references, a script's dependencies including every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
16866
+ description: "forward: what this resource loads, with each reference closing a cycle marked circular and the cycle under circular_references, a script's dependencies including every script whose class_name it uses, marked by_class with the name, and the script of every autoload it names, marked by_autoload with the autoload's name. An ext_resource whose uid names another file than its path is followed to that file, as the engine loads it, with the path written under stale_path; one whose uid names no file the project has is followed by its path, as the engine does after warning, with the uid under unknown_uid; and a load or preload of a uid the project does not know is listed under that uid as missing. A shader's #include is followed as the preprocessor reads it, relative to the shader. A file the depth stops at carries beyond_depth, how many files it names that were not walked. A file's dependencies are listed at its first entry, and again further down only where the depth cut that list short and more is left; every other entry for a file naming any carries dependencies_listed_above. summary.total_resources is the file asked about, so 1; total_dependencies counts each file reached, directly or through another, once however many places load it; total_references counts the entries in the tree, and missing names every file referenced that does not exist, with the files referring to it. reverse: every file that refers to it and how, for a file that is gone as well while anything still refers to it, answered with exists: false (a missing file nothing refers to is refused, naming the nearest files), a scene instancing it, a script extending, preloading or loading it, and for a script with a class_name every use of that name, and for an autoload's script every use of the autoload's name, with autoload naming it. Each reference carries a kind: ext_resource, extends, preload, load, include or path for a reference by path, with stale_path when an ext_resource reaches it by its uid under another path, and unknown_uid when one names it by path beside a uid naming no file; setting, plugin, export_preset and import for one in project.godot, a plugin.cfg, export_presets.cfg or an import sidecar; class_name for a use of the declared name, the declaration itself being the script asked about; autoload for code naming the autoload. Three kinds are not uses: doc is a name inside a ## documentation comment, which renaming would break in the generated docs; comment is one in ordinary prose, which it would only make stale; and stale_path is an ext_resource naming it by a path the engine does not load, since its uid leads to the file under loads. summary.in_code is the count with those left out, which is the number to read when the question is whether anything still uses this. Default forward."
16804
16867
  },
16805
16868
  depth: {
16806
16869
  type: "integer",
16807
16870
  minimum: 1,
16808
- description: "forward: how many levels to follow, 1 being what the file loads itself. Default unlimited."
16871
+ description: "forward: how many levels to follow, 1 being what the file loads itself. Default unlimited, which follows every class_name use too and in a project whose scripts name each other's classes reaches most of it: a game of a hundred scripts with large addons answered 488 files in about a megabyte from its main scene. A depth of 2 or 3 keeps the answer to what is near."
16809
16872
  },
16810
16873
  includeBuiltin: {
16811
16874
  type: "boolean",
@@ -16823,7 +16886,7 @@ var init_tool_definitions = __esm(() => {
16823
16886
  name: "project_import",
16824
16887
  title: "Asset imports",
16825
16888
  description: "The import pipeline: what needs importing, how a resource is imported, reimports, UIDs, and the global class list the editor and the engine read.",
16826
- answer: "reimport is 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. importedByTheScan names what the editor's scan imported first, and alsoImported, on a reimport of one resourcePath, the others it imported. wroteInProject names, by source, what an import wrote into the project itself rather than under .godot, such as the .translation a CSV gives each language, which version control sees and internationalization/locale/translations names. The import gives every script without a .uid sidecar one, as the editor's scan does, and uidsWritten names those scripts, counted per addon under uidsWrittenInAddons for the ones inside an addon. When nothing needed reimporting, the two lists are empty, a note says so, and there is no via. A whole-project reimport also answers missingSource, the .import files whose source is not beside them, empty when there are none, even when nothing needed reimporting; one resourcePath leaves it out. stillImporting is an editor that had not finished within timeoutMs.",
16889
+ answer: "options answers remap, deps and params as the .import file holds them, so an enum option such as compress/mode is its number: the engine gives scripts no list of a built-in importer's options or of what the numbers mean, so Godot's documentation of that importer is where a number is looked up. reimport is 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. importedByTheScan names what the editor's scan imported first, and alsoImported, on a reimport of one resourcePath, the others it imported. wroteInProject names, by source, what an import wrote into the project itself rather than under .godot, such as the .translation a CSV gives each language, which version control sees and internationalization/locale/translations names. The import gives every script without a .uid sidecar one, as the editor's scan does, and uidsWritten names those scripts, counted per addon under uidsWrittenInAddons for the ones inside an addon. When nothing needed reimporting, the two lists are empty, a note says so, and there is no via. A whole-project reimport also answers missingSource, the .import files whose source is not beside them, empty when there are none, even when nothing needed reimporting; one resourcePath leaves it out. stillImporting is an editor that had not finished within timeoutMs.",
16827
16890
  parameters: {
16828
16891
  projectPath: PROJECT_PATH,
16829
16892
  resourcePath: { ...RESOURCE_PATH, ops: ["status", "options", "set_options", "uid", "reimport"] },
@@ -16885,7 +16948,7 @@ var init_tool_definitions = __esm(() => {
16885
16948
  },
16886
16949
  refresh_classes: {
16887
16950
  effect: "destructive",
16888
- summary: 'rewrite .godot/global_script_class_cache.cfg from the class_name declarations on disk. That misses the list a running editor loaded, so "added: []" means the file was already right, not that nothing is wrong: unseenByEditor names a class the editor open on this project cannot resolve, and heldBehindInEditor one it holds compiled from an older text of its file. lostSinceLastRebuild names a class the cache held after the last rebuild and lost before this one: the editor wrote the file without it, on a save or a scan, and will again until restarted. staleInThisEngine names a class the rebuild added, unknown to the autoloads its own engine compiles afterwards, and autoloadsOnTheRebuiltList what a second engine on that list reports of them',
16951
+ summary: 'rewrite .godot/global_script_class_cache.cfg from the class_name declarations on disk. That misses the list a running editor loaded, so "added: []" means the file was already right, not that nothing is wrong: unseenByEditor names a class the editor open on this project cannot resolve, and heldBehindInEditor one it holds compiled from an older text of its file, with heldBehindUnchecked saying why when the editor did not say. lostSinceLastRebuild names a class the cache held after the last rebuild and lost before this one: the editor wrote the file without it, on a save or a scan, and will again until restarted. staleInThisEngine names a class the rebuild added, unknown to the autoloads its own engine compiles afterwards, and autoloadsOnTheRebuiltList what a second engine on that list reports of them',
16889
16952
  requires: []
16890
16953
  }
16891
16954
  }
@@ -16893,7 +16956,7 @@ var init_tool_definitions = __esm(() => {
16893
16956
  {
16894
16957
  name: "project_export",
16895
16958
  title: "Export",
16896
- description: "Export presets and exports. add_preset writes a preset to export_presets.cfg with the keys the editor's Project > Export writes for a new one and every option a new preset of its platform shows, each at its default, and makes it the platform's runnable preset when the platform has none. set_preset changes a preset's export path, its resource filter and the files it lists, its include and exclude filters, its custom features and its platform's options, and answers under changed what each held before and holds now, and under unchanged what already held the value given; nothing is written when any of it is refused. The option names are the exporter's own, as the Export dialog's Options tab shows them by their paths, such as binary_format/embed_pck, and a name the platform does not have is refused with the nearest it has, or all of them when none is near. An option the dialog shows only while another is on, such as codesign/identity behind codesign/enable, is taken when the preset has that one on or the same call turns it on, and otherwise refused naming what shows it. A secret option, such as a signing password, is written to .godot/export_credentials.cfg, where the editor keeps secrets out of the committed file, and its value is never answered. A file an option names that is not there is written and listed under missing_files, or for a secret its option under secretsNamingNoFile. remove_preset takes a preset out, and when it was its platform's runnable preset the next of that platform becomes it, under runnableNow, which is null when the platform has no other; a secret it held is taken out with it. It answers where this engine's export templates belong and whether the preset's platform has them, since an export needs them; list says the same for every preset. An editor open on the project does not see a preset added this way until it restarts. run names, as alsoWritten, what else the export wrote beside its output, such as the .pck a Windows or Linux build cannot start without. Its entries are the errors and warnings the export printed, the newest two hundred distinct ones, one printed more than once carrying times, and errors and warnings count them; exported is true only when this run wrote the output and printed no error.",
16959
+ description: "Export presets and exports. add_preset writes a preset to export_presets.cfg with the keys the editor's Project > Export writes for a new one and every option a new preset of its platform shows, each at its default, and makes it the platform's runnable preset when the platform has none. set_preset changes a preset's export path, its resource filter and the files it lists, its include and exclude filters, its custom features and its platform's options, and answers under changed what each held before and holds now, and under unchanged what already held the value given, with saved false when that is all of it, since the file is then not written; nothing is written when any of it is refused. The option names are the exporter's own, as the Export dialog's Options tab shows them by their paths, such as binary_format/embed_pck, and a name the platform does not have is refused with the nearest it has, or all of them when none is near. An option the dialog shows only while another is on, such as codesign/identity behind codesign/enable, is taken when the preset has that one on or the same call turns it on, and otherwise refused naming what shows it. A secret option, such as a signing password, is written to .godot/export_credentials.cfg, where the editor keeps secrets out of the committed file, and its value is never answered. A file an option names that is not there is written and listed under missing_files, or for a secret its option under secretsNamingNoFile. remove_preset takes a preset out, and when it was its platform's runnable preset the next of that platform becomes it, under runnableNow, which is null when the platform has no other; a secret it held is taken out with it. It answers where this engine's export templates belong and whether the preset's platform has them, since an export needs them; list says the same for every preset. An editor open on the project does not see a preset added this way until it restarts. run names, as alsoWritten, what else the export wrote beside its output, such as the .pck a Windows or Linux build cannot start without. Its entries are the errors and warnings the export printed, the newest two hundred distinct ones, one printed more than once carrying times, and errors and warnings count them; exported is true only when this run wrote the output and printed no error.",
16897
16960
  parameters: {
16898
16961
  projectPath: PROJECT_PATH,
16899
16962
  preset: {
@@ -16977,7 +17040,7 @@ var init_tool_definitions = __esm(() => {
16977
17040
  title: "Run tests",
16978
17041
  effect: "additive",
16979
17042
  description: "Runs the project's gdUnit4 tests and answers with every case that did not pass: where it is, and what the assertion said. Each entry under failed carries its first report as message and detail, and, when it has more than one, its reports under reports in the order the case met them: gdUnit4 goes on past a failed assertion, and a case can fail several and then stop on a runtime error, which makes its status error. Identical reports fold into one with times, at most twenty different ones are kept (reportsLeftOut counts the rest), and past about a megabyte the largest cases keep only their first message (failedWithoutDetail). failures counts the cases that failed an assertion and errors the cases that hit an error, so such a case counts in both. Suites where everything passed are counted, not listed, and an engine message keeps the frames above gdUnit4, not the twenty inside it; identical engine messages come back once under engineEntries, with times saying how often they were printed, and with script, line and function for the project script one points at, as editor_output's entries have. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes. What changed on disk since it was imported is then imported before any suite runs, and named under importedFirst. A suite it ran with no .uid beside it is named under uidsMissing, with uidsNote. On Windows and Linux the run gets a user:// of its own, away from a player's saves; on macOS the engine reads user:// off HOME, so it writes where the player keeps theirs, and savesNote says so. The run is headless unless a suite in it needs a window as gdharness test reads it, and then has one, on a desktop of its own on Windows. A run that found nothing to run is never called a pass, and names the path it looked in. A script that does not load stops gdUnit4 before any suite runs: its errors come back in full under scriptErrors, and the scripts that failed only because one they depend on did are listed by path under failedThroughDependency. When they failed on a class whose own script has a parser error, which gdUnit4 never names, that script is checked by the engine and its error comes first under scriptErrors, with the class it declares under declares, and the errors a script has only because that class did not resolve, which the engine reads as Variant, are kept apart under followFromUnresolved. An autoload that does not compile stops the run at once, its errors listed the same way. A client sending a progress token is told as each suite finishes, and at least every twenty seconds. A run that ends without a report, at its timeout or otherwise, names the suites it finished under suitesFinished, the failing ones with their failed cases under suitesFailing, and the one it was in under suiteRunning. When a client cuts a run off, that is kept in the project and the next project_test there carries it under previousRunCut. A run where nothing failed but orphan nodes were left comes back under warnings, with the count per suite: gdUnit4 decides its verdict on those and keeps them out of its report, so they are read off what it printed. A run whose every case passed is still not called passed when it printed a script error that no case failed on: gdUnit4 fails a case on the script errors its logger sees, and it cannot see a stack overflow, so the cases pass on whatever the faulted calls returned. Those errors come back under uncaughtScriptErrors, and under engineEntries by index with detailUnder rather than with their backtrace a second time; a backtrace that repeats, as a recursion's does, has each repeated frame or cycle of frames written once with the frames it covers and how many times. One raised inside assert_error(...).is_runtime_error is the test's own and is not listed there, and none are when the project turns off gdUnit4's report/godot/script_error setting; a suite declares them all the same. Needs gdUnit4 under addons/gdUnit4.",
16980
- answer: "passed is the verdict to read. exitCode is the engine's own exit, gdUnit4's code for what it saw (100 a case failed, 101 only warnings, 105 script errors it detected), and is 0 beside passed false when the run is failed for something gdUnit4 does not judge, such as an engine error no suite declared. time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for, and tookMsBy splits it into the class rebuild (classes), the import after it (import) and the engine running the suites (engine), each with any wait for an engine slot or the editor's scan in it. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, and each engine warning a suite printed and did not declare in its GDHARNESS_EXPECTED_WARNINGS, marked warning true, with the suite and the case that printed it, and declaredNotPrinted each declared error or warning its case did not print. gdUnit4 does not fail a case on push_error or push_warning, so a case that printed one undeclared is in neither failed nor errors: it is named here, and the run is failed for it. A declared text must be the whole line after ERROR: or WARNING: and its space, so a longer one its case printed holding the text is named under printedInstead. Either fails the run, and each suite holding one is listed with its count. leakedAtExit holds the engine's lines saying it exited still holding objects, resources, memory pages or a server's RIDs, which fail the run too, and the note names any script of the project Godot itself never frees and, on a run with engineArgs [\"--verbose\"], what the engine held. engineErrors counts every engine error the run printed but those a suite declared. orphans totals the orphan nodes gdUnit4 counted, and each suite that left some has its count, and a warning saying how to free them, under warnings, with left naming each case that left some (before or after for one of the suite's hooks, or the one value \"before or after\" where no case began and the two cannot be told apart) and the nodes, by class, name and script. suites lists only the suites with something to say (a case that failed, erred or was skipped, a hook that failed, orphans, or an error undeclared or declared and not printed), and suitesPassed counts the rest. A before() or after() that failed comes back under hookFailures with its suite, line and whole message; failures and errors count cases and leave it out, so the verdict names it as failed hooks, and the cases after a failed before() still run and are counted. Under classes, the classes each addon declares are counted under addedInAddons and changedInAddons rather than named, and a class the rebuild puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. A suite needs a window when it declares const GDHARNESS_NEEDS_WINDOW: bool = true, or calls scene_runner and declares nothing; a run holding one has a window, at the size project.godot gives its viewport and with no sound, and windowed is true and windowNote names those suites and any saying it runs headless that shared the window. On a machine with no display such a run is headless, windowed is false, and windowNote says so. " + IMPORTED_FIRST,
17043
+ answer: "passed is the verdict to read. exitCode is the engine's own exit, gdUnit4's code for what it saw (100 a case failed, 101 only warnings, 105 script errors it detected), and is 0 beside passed false when the run is failed for something gdUnit4 does not judge, such as an engine error no suite declared. time is gdUnit4's own sum of the seconds its cases took, from its report. tookMs is the whole call in milliseconds, from the class rebuild to the engine's exit, which is what a caller waits for, and tookMsBy splits it into the class rebuild (classes), the import after it (import) and the engine running the suites (engine), each with any wait for an engine slot or the editor's scan in it; a call that waited a second or more for slots in all is answered with an engine_slot_wait block giving waitedMs and who held them. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, and each engine warning a suite printed and did not declare in its GDHARNESS_EXPECTED_WARNINGS, marked warning true, with the suite and the case that printed it, and declaredNotPrinted each declared error or warning its case did not print. gdUnit4 does not fail a case on push_error or push_warning, so a case that printed one undeclared is in neither failed nor errors: it is named here, and the run is failed for it. A declared text must be the whole line after ERROR: or WARNING: and its space, so a longer one its case printed holding the text is named under printedInstead. Either fails the run, and each suite holding one is listed with its count. leakedAtExit holds the engine's lines saying it exited still holding objects, resources, memory pages or a server's RIDs, which fail the run too, and the note names any script of the project Godot itself never frees and, on a run with engineArgs [\"--verbose\"], what the engine held. engineErrors counts every engine error the run printed but those a suite declared. orphans totals the orphan nodes gdUnit4 counted, and each suite that left some has its count, and a warning saying how to free them, under warnings, with left naming each case that left some (before or after for one of the suite's hooks, or the one value \"before or after\" where no case began and the two cannot be told apart) and the nodes, by class, name and script. suites lists only the suites with something to say (a case that failed, erred or was skipped, a hook that failed, orphans, or an error undeclared or declared and not printed), and suitesPassed counts the rest. A before() or after() that failed comes back under hookFailures with its suite, line and whole message; failures and errors count cases and leave it out, so the verdict names it as failed hooks, and the cases after a failed before() still run and are counted. Under classes, the classes each addon declares are counted under addedInAddons and changedInAddons rather than named, and a class the rebuild puts back that the cache had after the last rebuild is named under classes.lostSinceLastRebuild, which is the editor writing its shorter list over the file on every save until restarted. A suite needs a window when it declares const GDHARNESS_NEEDS_WINDOW: bool = true, or calls scene_runner and declares nothing; a run holding one has a window, at the size project.godot gives its viewport and with no sound, and windowed is true and windowNote names those suites and any saying it runs headless that shared the window. On a machine with no display such a run is headless, windowed is false, and windowNote says so. " + IMPORTED_FIRST,
16981
17044
  parameters: {
16982
17045
  projectPath: PROJECT_PATH,
16983
17046
  path: {
@@ -17491,7 +17554,7 @@ var init_tool_definitions = __esm(() => {
17491
17554
  operations: {
17492
17555
  structure: {
17493
17556
  effect: "read",
17494
- summary: `functions (each parameter's default the source text after its =, so = null reads "null", and null for a parameter with none), variables (static ones marked is_static; one written with := has the type of a literal or built-in constructor it is given, marked inferred, as a constant always does), signals, constants, enums, inner_classes (each with its name, line and extends, and its own members in these same fields), class_name, extends and line_count, read from the file, and dependencies: the paths it loads or preloads as written, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies. A member or inner class with a ## documentation comment directly above it carries the comment's text as doc, and the script's own, the ## block above class_name or extends or one followed by a blank line before any member, comes back as class_doc`,
17557
+ summary: `functions (each parameter's default the source text after its =, so = null reads "null", and null for a parameter with none), variables (static ones marked is_static; one written with := has the type of a literal or built-in constructor it is given, marked inferred, as a constant always does), signals, constants, enums, inner_classes (each with its name, line and extends, and its own members in these same fields), class_name, extends and line_count, read from the file, and dependencies: the paths it loads or preloads as written, by a constant it declares too, and the paths it or an inner class extends by, then the scripts declaring each project class_name its code uses, which classes_used names with the class; autoloads_used names each autoload the code uses by its global name, with the autoload's script, which the project loads rather than this script, so it is not under dependencies. A member or inner class with a ## documentation comment directly above it carries the comment's text as doc, and the script's own, the ## block above class_name or extends or one followed by a blank line before any member, comes back as class_doc`,
17495
17558
  requires: []
17496
17559
  },
17497
17560
  symbols: {
@@ -17512,7 +17575,7 @@ var init_tool_definitions = __esm(() => {
17512
17575
  name: "script_diagnostics",
17513
17576
  title: "Script diagnostics",
17514
17577
  effect: "read",
17515
- description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine rather than language server: every error that stops a script compiling, up to its first syntax error, with its line and no column (a call to a method another class lacks is not one, since GDScript checks those only on self: the object may be a subclass that has it, so it fails at run time instead), dependenciesWithErrors naming the other scripts that failed while it loaded, typesMissingFromTheClassCache naming a class the project declares that the engine could not resolve because the class cache lacks it, with staleAnalysis saying what rewrites the cache, and the warnings at the levels the project sets, which a second engine reports by raising each warning set to warn as an error for that run only, each at its line with no column, warnings being null when that engine could not answer. Of several scripts, a language server that stops answering partway leaves the rest to the engine, and checkedBy then says language server, then engine, with checkedByLanguageServer counting the scripts it answered. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
17578
+ description: "Errors and warnings for a script, or for several, from the editor's language server, and whether each is clean. With no editor of this project open, so no language server of it to ask, a short headless engine compiles the scripts instead and the answer says checkedBy: engine rather than language server: every error that stops a script compiling, up to its first syntax error, with its line and no column (a method or property another class lacks is not one, since GDScript checks a method only on self and a property only named bare: the object may be a subclass that has it, so it fails at run time instead), dependenciesWithErrors naming the other scripts that failed while it loaded, typesMissingFromTheClassCache naming a class the project declares that the engine could not resolve because the class cache lacks it, with staleAnalysis saying what rewrites the cache, and the warnings at the levels the project sets, which a second engine reports by raising each warning set to warn as an error for that run only, each at its line with no column, warnings being null when that engine could not answer; a script with a syntax error is checked for no warnings until it parses. Of several scripts, a language server that stops answering partway leaves the rest to the engine, and checkedBy then says language server, then engine, with checkedByLanguageServer counting the scripts it answered. Each diagnostic carries its severity (error or warning), its message, and where it is as the editor shows it: line and column counted from 1, and endLine and endColumn just past its end. Diagnostics are checked against the project's own files, and the two that the files disprove are named rather than passed on: a member the class cache's file declares, or a project class it extends does, a method, property, constant, static function or enum member, comes back under contradictedByTheFile with the script declaring it, and so does one a diagnostic names alone as an external class member, read against the class it is looked up on in the line it is on; and a class this project declares that the diagnostic could not resolve comes back under typesTheEditorHasNotLoaded, with inTheClassCache saying whether a launched game would resolve it. staleAnalysis then says what to do, and for both states it is editor_rescan first: it asks the editor already running, starts nothing, and has cleared each in every reproduction measured. Two exceptions are named when they apply. For an enum member, constant or static function of a global class the reload comes first: in the one project that has reproduced a stale enum member, editor_rescan with reloadScript on the declaring script cleared it and a plain rescan did not. And an editor whose last scan wrote the class cache shorter than the file holds a list behind the files and writes it on every scan, so on that editor the rescan loads nothing and staleAnalysis says editor_launch restart instead. What follows differs: for a stale analysed type, editor_rescan with reloadScript recompiles the held copy and editor_launch restart is the last resort that costs a window; for a class missing from the cache, project_import refresh_classes rewrites the cache from disk at the cost of a short headless engine, which matters beside a running bench.",
17516
17579
  parameters: {
17517
17580
  projectPath: PROJECT_PATH,
17518
17581
  scriptPath: {
@@ -17899,7 +17962,8 @@ var init_tool_definitions = __esm(() => {
17899
17962
  name: "editor_rescan",
17900
17963
  title: "Rescan the project",
17901
17964
  effect: "additive",
17902
- description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it. waitedMs is the whole call, and waitedForEditorScanMs, given only when a scan or an import was already running, the part spent waiting it out. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script, measured against a real editor on three platforms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. A script that fails to compile is tried again once the scripts it is compiled against that the editor holds behind their files are reloaded, which are named under reloadedFirst. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
17965
+ description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script, measured against a real editor on three platforms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. A script that fails to compile is tried again once the scripts it is compiled against that the editor holds behind their files are reloaded, which are named under reloadedFirst. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
17966
+ answer: "waitedMs is the whole call, and waitedForEditorScanMs, given only when a scan or an import was already running, the part spent waiting it out. waitedForEditorAnswerMs, given when an idle editor took a second or more to say so, is how long that took: such an editor is busy on the thread that answers with an earlier request rather than scanning, so the time is added to timeoutMs rather than taken from it, and the note says so. stillWorking is true when a scan was still running at the end of the wait; the scan the call started is looked at once even when the wait has run out, so stillWorking is never said of a scan nothing looked at.",
17903
17967
  parameters: {
17904
17968
  projectPath: PROJECT_PATH,
17905
17969
  timeoutMs: {
@@ -18557,7 +18621,7 @@ var init_tool_definitions = __esm(() => {
18557
18621
  {
18558
18622
  name: "debug_state",
18559
18623
  title: "Debugger state",
18560
- description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debugger's console output so far, its last thousand lines, with any earlier ones counted under notShown. For a game this server started the output is what the game printed; the errors and warnings it reported, with where each happened, are in editor_output. For a game this server started, its keeper's debugger answers: each frame names its script as the project spells it under script and on this machine under path, and held says which thread stopped and why. For a game the editor plays, the editor's debug adapter answers. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack. A function that awaited is resumed with no caller below it, since the call that started it returned at the await, so its frame is the only one. The variables give an object as its class and instance id only, and evaluate reads into it: an expression worked out by the game at a frame, with the frame's locals, the globals and every global class in scope and the frame's instance as self, such as wave.spawned or wave.spawned.size(). The game runs what the expression calls, so a call that changes something changes the game. A frame with no instance, a static function's called on its class, is refused, since the engine lets the game go on rather than evaluate there; the frame below it has one. The compiler's own locals, named with @ such as a for loop's @counter_pos, are left out.",
18624
+ description: "Where the debugged game is stopped: the stack trace, what is in scope at a frame with the values, or the debugger's console output so far, its last thousand lines, with any earlier ones counted under notShown. For a game this server started the output is what the game printed; the errors and warnings it reported, with where each happened, are in editor_output, which also has what it printed once it has ended, as its debugger ends with it. For a game this server started, its keeper's debugger answers: each frame names its script as the project spells it under script and on this machine under path, and held says which thread stopped and why. For a game the editor plays, the editor's debug adapter answers. A stop is reported by the editor's adapter only to the sessions connected when it happened, so a server that connected afterwards, which is every server after a reconnect, is shown a thread and no frames about a game still sitting at its breakpoint. That server asks the runtime instead: a held game accepts a connection and never answers, and the refusal then says held rather than running, and that the stack cannot be read from here. debug_control continue lets it go, and the next stop is one this session is told about, with its stack. A function that awaited is resumed with no caller below it, since the call that started it returned at the await, so its frame is the only one. The variables give an object as its class and instance id only, and evaluate reads into it: an expression worked out by the game at a frame, with the frame's locals, the globals and every global class in scope and the frame's instance as self, such as wave.spawned or wave.spawned.size(). The game runs what the expression calls, so a call that changes something changes the game. A frame with no instance, a static function's called on its class, is refused, since the engine lets the game go on rather than evaluate there; the frame below it has one. The compiler's own locals, named with @ such as a for loop's @counter_pos, are left out.",
18561
18625
  parameters: {
18562
18626
  frameId: {
18563
18627
  type: "number",
@@ -18698,9 +18762,9 @@ function whatItCanChange(tool) {
18698
18762
  if (destroying.length === 0) {
18699
18763
  return "Adds or starts things, and overwrites and removes nothing itself.";
18700
18764
  }
18701
- return `Can overwrite, remove or end what is there, through ${listed2(destroying)}.`;
18765
+ return `Can overwrite, remove or end what is there, through ${listed3(destroying)}.`;
18702
18766
  }
18703
- function listed2(items) {
18767
+ function listed3(items) {
18704
18768
  const last = items.at(-1) ?? "";
18705
18769
  return items.length === 1 ? last : `${items.slice(0, -1).join(", ")} and ${last}`;
18706
18770
  }
@@ -18758,8 +18822,8 @@ var init_tool_reference = __esm(() => {
18758
18822
  });
18759
18823
 
18760
18824
  // src/suite-plan.ts
18761
- import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync20 } from "node:fs";
18762
- import { dirname as dirname11, join as join25, posix as posix3 } from "node:path";
18825
+ import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync21 } from "node:fs";
18826
+ import { dirname as dirname11, join as join26, posix as posix3 } from "node:path";
18763
18827
  function within(path, folder) {
18764
18828
  return path === folder || path.startsWith(`${folder}/`);
18765
18829
  }
@@ -18775,7 +18839,7 @@ class ProjectScripts {
18775
18839
  this.projectPath = projectPath;
18776
18840
  let settings = {};
18777
18841
  try {
18778
- settings = parseProjectGodot(readFileSync20(join25(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18842
+ settings = parseProjectGodot(readFileSync21(join26(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18779
18843
  } catch {}
18780
18844
  const [section, key] = [NOT_SEARCHED_SETTING, GUESS_WINDOW_SETTING].map((setting) => setting.slice("gdharness/".length));
18781
18845
  const listed = settings[section ?? ""];
@@ -18890,7 +18954,7 @@ class ProjectScripts {
18890
18954
  }
18891
18955
  function readTimes(file) {
18892
18956
  try {
18893
- const parsed = JSON.parse(readFileSync20(file, "utf8"));
18957
+ const parsed = JSON.parse(readFileSync21(file, "utf8"));
18894
18958
  return typeof parsed === "object" && parsed !== null ? parsed : {};
18895
18959
  } catch {
18896
18960
  return {};
@@ -19173,7 +19237,7 @@ function viewportOf(projectPath) {
19173
19237
  if (!existsSync22(file)) {
19174
19238
  return null;
19175
19239
  }
19176
- const text = readFileSync20(file, "utf8");
19240
+ const text = readFileSync21(file, "utf8");
19177
19241
  const width = /^window\/size\/viewport_width\s*=\s*(\d+)/m.exec(text)?.[1];
19178
19242
  const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
19179
19243
  return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
@@ -40792,13 +40856,13 @@ var init_stdio = __esm(() => {
40792
40856
  });
40793
40857
 
40794
40858
  // src/autoload-failure.ts
40795
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
40796
- import { join as join28 } from "node:path";
40859
+ import { existsSync as existsSync24, readFileSync as readFileSync24 } from "node:fs";
40860
+ import { join as join29 } from "node:path";
40797
40861
  function scriptsAutoloaded(projectPath) {
40798
40862
  const scripts = new Map;
40799
40863
  let settings;
40800
40864
  try {
40801
- settings = parseProjectGodot(readFileSync23(join28(projectPath, "project.godot"), "utf8"));
40865
+ settings = parseProjectGodot(readFileSync24(join29(projectPath, "project.godot"), "utf8"));
40802
40866
  } catch {
40803
40867
  return scripts;
40804
40868
  }
@@ -40807,12 +40871,12 @@ function scriptsAutoloaded(projectPath) {
40807
40871
  scripts.set(`res://${path}`, name);
40808
40872
  continue;
40809
40873
  }
40810
- const scene = join28(projectPath, path);
40874
+ const scene = join29(projectPath, path);
40811
40875
  if (!existsSync24(scene)) {
40812
40876
  continue;
40813
40877
  }
40814
40878
  try {
40815
- for (const named of readFileSync23(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
40879
+ for (const named of readFileSync24(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
40816
40880
  scripts.set(named[1] ?? "", name);
40817
40881
  }
40818
40882
  } catch {}
@@ -40831,10 +40895,10 @@ var init_autoload_failure = __esm(() => {
40831
40895
  });
40832
40896
 
40833
40897
  // src/runtime-client.ts
40834
- import { existsSync as existsSync25, readdirSync as readdirSync15, readFileSync as readFileSync24, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
40898
+ import { existsSync as existsSync25, readdirSync as readdirSync15, readFileSync as readFileSync25, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
40835
40899
  import { createConnection as createConnection2 } from "node:net";
40836
40900
  import { tmpdir as tmpdir5 } from "node:os";
40837
- import { dirname as dirname14, join as join29, resolve as resolve11 } from "node:path";
40901
+ import { dirname as dirname14, join as join30, resolve as resolve11 } from "node:path";
40838
40902
  import { setTimeout as delay5 } from "node:timers/promises";
40839
40903
  function runtimeDirectory(variables = process.env) {
40840
40904
  const explicit = envValue("GDHARNESS_RUNTIME_DIR", variables);
@@ -40842,19 +40906,19 @@ function runtimeDirectory(variables = process.env) {
40842
40906
  return explicit;
40843
40907
  }
40844
40908
  const perUser = envValue("XDG_RUNTIME_DIR", variables);
40845
- return join29(perUser ?? tmpdir5(), "gdharness");
40909
+ return join30(perUser ?? tmpdir5(), "gdharness");
40846
40910
  }
40847
40911
  function runtimeDirectories(variables = process.env) {
40848
40912
  const candidates = [runtimeDirectory(variables)];
40849
40913
  const fallbacks = process.platform === "win32" ? [envValue("SystemRoot", variables) ?? envValue("windir", variables) ?? "C:\\Windows"] : ["/tmp", "/var/tmp"];
40850
40914
  for (const base of fallbacks) {
40851
- candidates.push(join29(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
40915
+ candidates.push(join30(base, process.platform === "win32" ? "Temp" : "", "gdharness"));
40852
40916
  }
40853
40917
  return [...new Set(candidates.map((path) => resolve11(path)))];
40854
40918
  }
40855
40919
  function errorReportOf(pid, directories = runtimeDirectories()) {
40856
40920
  for (const directory of directories) {
40857
- const path = join29(directory, `runtime-${pid}.log`);
40921
+ const path = join30(directory, `runtime-${pid}.log`);
40858
40922
  if (existsSync25(path)) {
40859
40923
  return path;
40860
40924
  }
@@ -40886,7 +40950,7 @@ function reportAlreadyInTranscript(transcript, report) {
40886
40950
  function parseAnnouncement(file, pid) {
40887
40951
  let fields;
40888
40952
  try {
40889
- fields = asParams(JSON.parse(readFileSync24(file, "utf8")));
40953
+ fields = asParams(JSON.parse(readFileSync25(file, "utf8")));
40890
40954
  } catch {
40891
40955
  return { kind: "rubbish" };
40892
40956
  }
@@ -40958,7 +41022,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
40958
41022
  function wentAway(file, pid) {
40959
41023
  let project = "";
40960
41024
  try {
40961
- const fields = asParams(JSON.parse(readFileSync24(file, "utf8")));
41025
+ const fields = asParams(JSON.parse(readFileSync25(file, "utf8")));
40962
41026
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
40963
41027
  } catch {
40964
41028
  return;
@@ -41010,14 +41074,14 @@ function announcedIn(directory) {
41010
41074
  for (const entry of readdirSync15(directory)) {
41011
41075
  const report = ERROR_REPORT_PATTERN.exec(entry);
41012
41076
  if (report) {
41013
- sweepErrorReport(join29(directory, entry), Number.parseInt(report[1] ?? "", 10));
41077
+ sweepErrorReport(join30(directory, entry), Number.parseInt(report[1] ?? "", 10));
41014
41078
  continue;
41015
41079
  }
41016
41080
  const match = ANNOUNCEMENT_PATTERN.exec(entry);
41017
41081
  if (!match) {
41018
41082
  continue;
41019
41083
  }
41020
- const file = join29(directory, entry);
41084
+ const file = join30(directory, entry);
41021
41085
  const pid = Number.parseInt(match[1] ?? "", 10);
41022
41086
  const running = alive(pid);
41023
41087
  entries.push({ file, pid, running });
@@ -41045,7 +41109,7 @@ function announcedIn(directory) {
41045
41109
  found.push(announced);
41046
41110
  }
41047
41111
  const held = new Set(entries.map((one) => one.file));
41048
- const here = join29(directory, ".");
41112
+ const here = join30(directory, ".");
41049
41113
  for (const file of verdicts.keys()) {
41050
41114
  if (dirname14(file) === here && !held.has(file)) {
41051
41115
  verdicts.delete(file);
@@ -41054,7 +41118,7 @@ function announcedIn(directory) {
41054
41118
  return found;
41055
41119
  }
41056
41120
  async function announcementEnded(pid, directories = runtimeDirectories()) {
41057
- const files = directories.map((directory) => join29(directory, `runtime-${pid}.json`)).filter((file) => existsSync25(file));
41121
+ const files = directories.map((directory) => join30(directory, `runtime-${pid}.json`)).filter((file) => existsSync25(file));
41058
41122
  const first = files[0];
41059
41123
  if (first === undefined) {
41060
41124
  return;
@@ -41473,14 +41537,14 @@ var init_runtime_client = __esm(() => {
41473
41537
  });
41474
41538
 
41475
41539
  // src/boot-note.ts
41476
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync25, writeFileSync as writeFileSync13 } from "node:fs";
41477
- import { dirname as dirname15, join as join30 } from "node:path";
41540
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync26, writeFileSync as writeFileSync13 } from "node:fs";
41541
+ import { dirname as dirname15, join as join31 } from "node:path";
41478
41542
  function bootNotePath(projectPath) {
41479
- return join30(projectPath, ".godot", "gdharness-boot.json");
41543
+ return join31(projectPath, ".godot", "gdharness-boot.json");
41480
41544
  }
41481
41545
  function readScenes(projectPath) {
41482
41546
  try {
41483
- return asParams(asParams(JSON.parse(readFileSync25(bootNotePath(projectPath), "utf8")))["scenes"]);
41547
+ return asParams(asParams(JSON.parse(readFileSync26(bootNotePath(projectPath), "utf8")))["scenes"]);
41484
41548
  } catch {
41485
41549
  return {};
41486
41550
  }
@@ -41522,10 +41586,10 @@ var init_boot_note = __esm(() => {
41522
41586
  });
41523
41587
 
41524
41588
  // src/breakpoint-note.ts
41525
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync13, writeFileSync as writeFileSync14 } from "node:fs";
41526
- import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as relative6 } from "node:path";
41589
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync27, rmSync as rmSync13, writeFileSync as writeFileSync14 } from "node:fs";
41590
+ import { dirname as dirname16, isAbsolute as isAbsolute7, join as join32, relative as relative6 } from "node:path";
41527
41591
  function breakpointNotePath(projectPath) {
41528
- return join31(projectPath, ".godot", "gdharness-breakpoints.json");
41592
+ return join32(projectPath, ".godot", "gdharness-breakpoints.json");
41529
41593
  }
41530
41594
  function writeBreakpointNote(projectPath, held) {
41531
41595
  const path = breakpointNotePath(projectPath);
@@ -41551,7 +41615,7 @@ function writeBreakpointNote(projectPath, held) {
41551
41615
  }
41552
41616
  function readBreakpointNote(projectPath) {
41553
41617
  try {
41554
- const parsed = JSON.parse(readFileSync26(breakpointNotePath(projectPath), "utf8"));
41618
+ const parsed = JSON.parse(readFileSync27(breakpointNotePath(projectPath), "utf8"));
41555
41619
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
41556
41620
  return [];
41557
41621
  }
@@ -41566,7 +41630,7 @@ function readBreakpointNote(projectPath) {
41566
41630
  }
41567
41631
  const lines = Array.isArray(listed) ? listed.filter((line) => Number.isInteger(line) && line > 0) : [];
41568
41632
  if (lines.length > 0) {
41569
- held.push({ scriptPath: realAsFarAsItExists(join31(projectPath, script)), lines });
41633
+ held.push({ scriptPath: realAsFarAsItExists(join32(projectPath, script)), lines });
41570
41634
  }
41571
41635
  }
41572
41636
  return held;
@@ -41584,14 +41648,14 @@ var init_breakpoint_note = __esm(() => {
41584
41648
  });
41585
41649
 
41586
41650
  // src/class-note.ts
41587
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync27 } from "node:fs";
41588
- import { dirname as dirname17, join as join32 } from "node:path";
41651
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync28 } from "node:fs";
41652
+ import { dirname as dirname17, join as join33 } from "node:path";
41589
41653
  function classNotePath(projectPath) {
41590
- return join32(projectPath, ".godot", "gdharness-classes.json");
41654
+ return join33(projectPath, ".godot", "gdharness-classes.json");
41591
41655
  }
41592
41656
  function readClassNote(projectPath) {
41593
41657
  try {
41594
- const fields = asParams(JSON.parse(readFileSync27(classNotePath(projectPath), "utf8")));
41658
+ const fields = asParams(JSON.parse(readFileSync28(classNotePath(projectPath), "utf8")));
41595
41659
  const rebuiltWith = readArray(fields, "rebuiltWith");
41596
41660
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
41597
41661
  return null;
@@ -41633,16 +41697,16 @@ var init_class_note = __esm(() => {
41633
41697
  });
41634
41698
 
41635
41699
  // src/config-pin.ts
41636
- import { existsSync as existsSync26, readFileSync as readFileSync28 } from "node:fs";
41637
- import { join as join33 } from "node:path";
41700
+ import { existsSync as existsSync26, readFileSync as readFileSync29 } from "node:fs";
41701
+ import { join as join34 } from "node:path";
41638
41702
  function versionsNamedInConfig(projectPath) {
41639
- const config = join33(projectPath, ".mcp.json");
41703
+ const config = join34(projectPath, ".mcp.json");
41640
41704
  if (!existsSync26(config)) {
41641
41705
  return [];
41642
41706
  }
41643
41707
  let text;
41644
41708
  try {
41645
- text = readFileSync28(config, "utf8");
41709
+ text = readFileSync29(config, "utf8");
41646
41710
  JSON.parse(text);
41647
41711
  } catch {
41648
41712
  return [];
@@ -41663,10 +41727,10 @@ var init_config_pin = __esm(() => {
41663
41727
  });
41664
41728
 
41665
41729
  // src/cut-test-run.ts
41666
- import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync29, rmSync as rmSync14, writeFileSync as writeFileSync15 } from "node:fs";
41667
- import { dirname as dirname18, join as join34 } from "node:path";
41730
+ import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync30, rmSync as rmSync14, writeFileSync as writeFileSync15 } from "node:fs";
41731
+ import { dirname as dirname18, join as join35 } from "node:path";
41668
41732
  function keptAt(projectPath) {
41669
- return join34(projectPath, ".godot", "gdharness-reports", "cut-run.json");
41733
+ return join35(projectPath, ".godot", "gdharness-reports", "cut-run.json");
41670
41734
  }
41671
41735
  function keepCutTestRun(projectPath, run) {
41672
41736
  const path = keptAt(projectPath);
@@ -41682,7 +41746,7 @@ function takeCutTestRun(projectPath) {
41682
41746
  return null;
41683
41747
  }
41684
41748
  try {
41685
- const run = JSON.parse(readFileSync29(path, "utf8"));
41749
+ const run = JSON.parse(readFileSync30(path, "utf8"));
41686
41750
  rmSync14(path, { force: true });
41687
41751
  return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
41688
41752
  } catch {
@@ -42418,22 +42482,22 @@ import {
42418
42482
  existsSync as existsSync28,
42419
42483
  mkdirSync as mkdirSync15,
42420
42484
  openSync as openSync4,
42421
- readFileSync as readFileSync30,
42485
+ readFileSync as readFileSync31,
42422
42486
  readSync as readSync2,
42423
42487
  rmSync as rmSync15,
42424
42488
  statSync as statSync16,
42425
42489
  writeFileSync as writeFileSync16
42426
42490
  } from "node:fs";
42427
- import { dirname as dirname19, join as join35 } from "node:path";
42491
+ import { dirname as dirname19, join as join36 } from "node:path";
42428
42492
  function editorLogPath(projectPath) {
42429
- return join35(projectPath, ".godot", "gdharness-editor.log");
42493
+ return join36(projectPath, ".godot", "gdharness-editor.log");
42430
42494
  }
42431
42495
  function editorLogNotePath(projectPath) {
42432
- return join35(projectPath, ".godot", "gdharness-editor.json");
42496
+ return join36(projectPath, ".godot", "gdharness-editor.json");
42433
42497
  }
42434
42498
  function readEditorLogNote(projectPath) {
42435
42499
  try {
42436
- const fields = asParams(JSON.parse(readFileSync30(editorLogNotePath(projectPath), "utf8")));
42500
+ const fields = asParams(JSON.parse(readFileSync31(editorLogNotePath(projectPath), "utf8")));
42437
42501
  const pid = readNumber(fields, "pid");
42438
42502
  const startedAt = readString(fields, "startedAt");
42439
42503
  const processStartedAt = readNumber(fields, "processStartedAt");
@@ -42537,7 +42601,7 @@ function foldProgress(text) {
42537
42601
  function theFirstOf(path) {
42538
42602
  const whole = statSync16(path).size;
42539
42603
  if (whole <= MOST_CONSOLE_BYTES) {
42540
- return { text: readFileSync30(path, "utf8") };
42604
+ return { text: readFileSync31(path, "utf8") };
42541
42605
  }
42542
42606
  const handle = openSync4(path, "r");
42543
42607
  try {
@@ -42698,7 +42762,7 @@ var init_editor_log = __esm(() => {
42698
42762
  });
42699
42763
 
42700
42764
  // src/rename.ts
42701
- import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync31, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync17 } from "node:fs";
42765
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync32, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync17 } from "node:fs";
42702
42766
  import { dirname as dirname20, posix as posix4 } from "node:path";
42703
42767
 
42704
42768
  class Script {
@@ -43403,7 +43467,7 @@ function rewritesOf(plan, texts, move) {
43403
43467
  }
43404
43468
  function applyRewrites(rewrites) {
43405
43469
  for (const rewrite of rewrites) {
43406
- const now = readFileSync31(rewrite.absolute, "utf8");
43470
+ const now = readFileSync32(rewrite.absolute, "utf8");
43407
43471
  if (now !== rewrite.before) {
43408
43472
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
43409
43473
  }
@@ -43468,7 +43532,7 @@ var init_rename = __esm(() => {
43468
43532
 
43469
43533
  // src/rename-symbol.ts
43470
43534
  import { existsSync as existsSync30, mkdirSync as mkdirSync17, readdirSync as readdirSync16, realpathSync as realpathSync4, rmSync as rmSync16, writeFileSync as writeFileSync18 } from "node:fs";
43471
- import { basename as basename9, dirname as dirname21, join as join36, relative as relative7, resolve as resolve12 } from "node:path";
43535
+ import { basename as basename9, dirname as dirname21, join as join37, relative as relative7, resolve as resolve12 } from "node:path";
43472
43536
  function resourcePathOf(projectPath, absolute) {
43473
43537
  const inside = relative7(onDiskSpelling(projectPath), onDiskSpelling(absolute));
43474
43538
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
@@ -43481,7 +43545,7 @@ function onDiskSpelling(path) {
43481
43545
  let at = resolve12(path);
43482
43546
  for (;; ) {
43483
43547
  try {
43484
- return join36(realpathSync4.native(at), ...missing.reverse());
43548
+ return join37(realpathSync4.native(at), ...missing.reverse());
43485
43549
  } catch {
43486
43550
  const parent = dirname21(at);
43487
43551
  if (parent === at) {
@@ -43493,18 +43557,18 @@ function onDiskSpelling(path) {
43493
43557
  }
43494
43558
  }
43495
43559
  function usedAsIdentifier(scripts, name, most) {
43496
- const found = [];
43560
+ const found = new Set;
43497
43561
  for (const script of scripts.values()) {
43498
43562
  for (const occurrence of script.occurrences(name)) {
43499
43563
  if (occurrence.kind === "code" && !occurrence.afterDot) {
43500
- found.push(`${script.path}:${script.lines.positionOf(occurrence.offset).line + 1}`);
43501
- if (found.length >= most) {
43502
- return found;
43564
+ found.add(`${script.path}:${script.lines.positionOf(occurrence.offset).line + 1}`);
43565
+ if (found.size >= most) {
43566
+ return [...found];
43503
43567
  }
43504
43568
  }
43505
43569
  }
43506
43570
  }
43507
- return found;
43571
+ return [...found];
43508
43572
  }
43509
43573
  function autoloadNames(texts) {
43510
43574
  const settings = texts.find((file) => file.path === "res://project.godot");
@@ -43568,7 +43632,7 @@ async function renameSymbol(request, services) {
43568
43632
  conflicts.push(`${newName} is already an autoload in project.godot`);
43569
43633
  }
43570
43634
  for (const place of usedAsIdentifier(scripts, newName, 10)) {
43571
- conflicts.push(`${newName} is already an identifier at ${place}`);
43635
+ conflicts.push(`${newName} is already used in code at ${place}`);
43572
43636
  }
43573
43637
  const taken = (await services.namesTaken([newName], null))[newName];
43574
43638
  if (taken !== undefined && taken.as.length > 0) {
@@ -43919,15 +43983,15 @@ ${mention.kind}`;
43919
43983
  };
43920
43984
  }
43921
43985
  function writeLeftAloneDetail(projectPath, symbol, detail) {
43922
- const directory = join36(projectPath, ".godot", "gdharness-renames");
43986
+ const directory = join37(projectPath, ".godot", "gdharness-renames");
43923
43987
  mkdirSync17(directory, { recursive: true });
43924
43988
  detailWrites += 1;
43925
- const path = join36(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
43989
+ const path = join37(directory, `${String(Date.now())}-${String(detailWrites).padStart(6, "0")}-${String(process.pid)}-${symbol}.json`);
43926
43990
  writeFileSync18(path, `${JSON.stringify(detail, null, 2)}
43927
43991
  `, "utf8");
43928
43992
  const kept = readdirSync16(directory).filter((name) => name.endsWith(".json")).sort().reverse();
43929
43993
  for (const old of kept.slice(10)) {
43930
- rmSync16(join36(directory, old), { force: true });
43994
+ rmSync16(join37(directory, old), { force: true });
43931
43995
  }
43932
43996
  return path;
43933
43997
  }
@@ -43971,15 +44035,15 @@ import {
43971
44035
  existsSync as existsSync31,
43972
44036
  mkdirSync as mkdirSync18,
43973
44037
  readdirSync as readdirSync17,
43974
- readFileSync as readFileSync32,
44038
+ readFileSync as readFileSync33,
43975
44039
  renameSync as renameSync4,
43976
44040
  statSync as statSync17,
43977
44041
  writeFileSync as writeFileSync19
43978
44042
  } from "node:fs";
43979
- import { basename as basename10, dirname as dirname22, extname as extname3, join as join37, relative as relative8 } from "node:path";
44043
+ import { basename as basename10, dirname as dirname22, extname as extname3, join as join38, relative as relative8 } from "node:path";
43980
44044
  function filesUnder(directory) {
43981
44045
  return readdirSync17(directory, { withFileTypes: true }).flatMap((entry) => {
43982
- const path = join37(directory, entry.name);
44046
+ const path = join38(directory, entry.name);
43983
44047
  return entry.isDirectory() ? filesUnder(path) : [path];
43984
44048
  });
43985
44049
  }
@@ -44000,8 +44064,8 @@ function unseenByTheEngine(projectPath, path) {
44000
44064
  return `${path} is under ${step}, and the engine passes over every directory whose name starts with a dot`;
44001
44065
  }
44002
44066
  if (at < steps.length - 1) {
44003
- directory = join37(directory, step);
44004
- if (existsSync31(join37(directory, ".gdignore"))) {
44067
+ directory = join38(directory, step);
44068
+ if (existsSync31(join38(directory, ".gdignore"))) {
44005
44069
  return `${path} is under a directory holding a .gdignore, which the engine does not load`;
44006
44070
  }
44007
44071
  }
@@ -44013,12 +44077,12 @@ function installedAddonOf(projectPath, path) {
44013
44077
  if (steps[0] !== "addons" || steps[1] === undefined) {
44014
44078
  return null;
44015
44079
  }
44016
- return existsSync31(join37(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
44080
+ return existsSync31(join38(projectPath, "addons", steps[1], VERSION_MARKER)) ? `res://addons/${steps[1]}` : null;
44017
44081
  }
44018
44082
  function binaryResources(projectPath) {
44019
44083
  const found = [];
44020
44084
  const visit = (directory, prefix) => {
44021
- if (existsSync31(join37(directory, ".gdignore"))) {
44085
+ if (existsSync31(join38(directory, ".gdignore"))) {
44022
44086
  return;
44023
44087
  }
44024
44088
  for (const entry of readdirSync17(directory, { withFileTypes: true })) {
@@ -44027,7 +44091,7 @@ function binaryResources(projectPath) {
44027
44091
  }
44028
44092
  const path = `${prefix}${entry.name}`;
44029
44093
  if (entry.isDirectory()) {
44030
- visit(join37(directory, entry.name), `${path}/`);
44094
+ visit(join38(directory, entry.name), `${path}/`);
44031
44095
  } else if (/\.(scn|res)$/i.test(entry.name)) {
44032
44096
  found.push(path);
44033
44097
  }
@@ -44037,7 +44101,7 @@ function binaryResources(projectPath) {
44037
44101
  return found;
44038
44102
  }
44039
44103
  function targetPathOf(projectPath, absolute) {
44040
- const inside = relative8(onDiskSpelling(projectPath), join37(onDiskSpelling(dirname22(absolute)), basename10(absolute)));
44104
+ const inside = relative8(onDiskSpelling(projectPath), join38(onDiskSpelling(dirname22(absolute)), basename10(absolute)));
44041
44105
  if (inside === "" || inside.startsWith("..") || /^[A-Za-z]:/.test(inside)) {
44042
44106
  return null;
44043
44107
  }
@@ -44092,7 +44156,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44092
44156
  return { ok: false, reason: `${unseen}, so nothing there could load what was moved.` };
44093
44157
  }
44094
44158
  if (!isDirectory) {
44095
- const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync31(join37(projectPath, path.slice("res://".length))));
44159
+ const owner = SIDECARS.map((sidecar) => from.endsWith(sidecar) ? from.slice(0, -sidecar.length) : null).find((path) => path !== null && existsSync31(join38(projectPath, path.slice("res://".length))));
44096
44160
  if (owner !== undefined && owner !== null) {
44097
44161
  return { ok: false, reason: `${from} belongs to ${owner} and moves with it: move ${owner} instead.` };
44098
44162
  }
@@ -44126,12 +44190,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44126
44190
  scripts,
44127
44191
  moves,
44128
44192
  named: [from],
44129
- exists: (path) => existsSync31(join37(projectPath, path.slice("res://".length)))
44193
+ exists: (path) => existsSync31(join38(projectPath, path.slice("res://".length)))
44130
44194
  }, { edits: [], mentions: [] });
44131
44195
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
44132
44196
  const binaries = [];
44133
44197
  for (const path of binaryResources(projectPath)) {
44134
- const bytes = readFileSync32(join37(projectPath, path.slice("res://".length)));
44198
+ const bytes = readFileSync33(join38(projectPath, path.slice("res://".length)));
44135
44199
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
44136
44200
  if (names !== undefined) {
44137
44201
  binaries.push({
@@ -44150,7 +44214,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44150
44214
  file: afterwards(mention.file)
44151
44215
  }));
44152
44216
  const left = leftAloneReport(mentions);
44153
- const absoluteOf = (path) => join37(projectPath, path.slice("res://".length));
44217
+ const absoluteOf = (path) => join38(projectPath, path.slice("res://".length));
44154
44218
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
44155
44219
  const moved = [...moves].map(([source, target]) => {
44156
44220
  const rewrite = rewritten.get(source);
@@ -44192,7 +44256,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44192
44256
  }
44193
44257
  function applyFileMove(outcome) {
44194
44258
  for (const rewrite of outcome.inPlace) {
44195
- if (readFileSync32(rewrite.absolute, "utf8") !== rewrite.before) {
44259
+ if (readFileSync33(rewrite.absolute, "utf8") !== rewrite.before) {
44196
44260
  throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
44197
44261
  }
44198
44262
  }
@@ -44200,7 +44264,7 @@ function applyFileMove(outcome) {
44200
44264
  if (!existsSync31(file.absoluteFrom)) {
44201
44265
  throw new Error(`${file.from} is gone from disk; nothing was written`);
44202
44266
  }
44203
- if (file.text !== null && readFileSync32(file.absoluteFrom, "utf8") !== file.text.before) {
44267
+ if (file.text !== null && readFileSync33(file.absoluteFrom, "utf8") !== file.text.before) {
44204
44268
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
44205
44269
  }
44206
44270
  if (existsSync31(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
@@ -47941,10 +48005,10 @@ var init_keeper_channel = () => {};
47941
48005
 
47942
48006
  // src/lifecycle-log.ts
47943
48007
  import { appendFileSync, mkdirSync as mkdirSync19, renameSync as renameSync5, statSync as statSync18 } from "node:fs";
47944
- import { dirname as dirname23, join as join38 } from "node:path";
48008
+ import { dirname as dirname23, join as join39 } from "node:path";
47945
48009
  import process16 from "node:process";
47946
48010
  function lifecycleLogPath() {
47947
- return join38(runtimeDirectory(), "servers.log");
48011
+ return join39(runtimeDirectory(), "servers.log");
47948
48012
  }
47949
48013
  function writeLifecycle(event, fields = {}) {
47950
48014
  const path = lifecycleLogPath();
@@ -48314,10 +48378,10 @@ var init_prompts = __esm(() => {
48314
48378
  });
48315
48379
 
48316
48380
  // src/restart-note.ts
48317
- import { mkdirSync as mkdirSync20, readFileSync as readFileSync33, rmSync as rmSync17, writeFileSync as writeFileSync20 } from "node:fs";
48318
- import { dirname as dirname24, join as join39 } from "node:path";
48381
+ import { mkdirSync as mkdirSync20, readFileSync as readFileSync34, rmSync as rmSync17, writeFileSync as writeFileSync20 } from "node:fs";
48382
+ import { dirname as dirname24, join as join40 } from "node:path";
48319
48383
  function restartNotePath(projectPath) {
48320
- return join39(projectPath, ".godot", "gdharness-restart.json");
48384
+ return join40(projectPath, ".godot", "gdharness-restart.json");
48321
48385
  }
48322
48386
  function noteRestartBegun(note) {
48323
48387
  const path = restartNotePath(note.projectPath);
@@ -48331,7 +48395,7 @@ function noteRestartBegun(note) {
48331
48395
  }
48332
48396
  function restartOwed(projectPath) {
48333
48397
  try {
48334
- const parsed = JSON.parse(readFileSync33(restartNotePath(projectPath), "utf8"));
48398
+ const parsed = JSON.parse(readFileSync34(restartNotePath(projectPath), "utf8"));
48335
48399
  if (typeof parsed !== "object" || parsed === null) {
48336
48400
  return null;
48337
48401
  }
@@ -48357,10 +48421,10 @@ var init_restart_note = __esm(() => {
48357
48421
 
48358
48422
  // src/run-record.ts
48359
48423
  import { createHash as createHash7 } from "node:crypto";
48360
- import { mkdirSync as mkdirSync21, openSync as openSync5, readdirSync as readdirSync18, readFileSync as readFileSync34, rmSync as rmSync18, statSync as statSync19 } from "node:fs";
48361
- import { basename as basename11, join as join40, resolve as resolve13 } from "node:path";
48424
+ import { mkdirSync as mkdirSync21, openSync as openSync5, readdirSync as readdirSync18, readFileSync as readFileSync35, rmSync as rmSync18, statSync as statSync19 } from "node:fs";
48425
+ import { basename as basename11, join as join41, resolve as resolve13 } from "node:path";
48362
48426
  function runsDirectory() {
48363
- return join40(runtimeDirectory(), "runs");
48427
+ return join41(runtimeDirectory(), "runs");
48364
48428
  }
48365
48429
  function projectKey(projectPath) {
48366
48430
  const real = realAsFarAsItExists(resolve13(projectPath)).replace(/[\\/]+$/, "");
@@ -48368,10 +48432,10 @@ function projectKey(projectPath) {
48368
48432
  return createHash7("sha256").update(folded.replaceAll("\\", "/")).digest("hex").slice(0, 16);
48369
48433
  }
48370
48434
  function runRecordPath(projectPath, directory = runtimeDirectory()) {
48371
- return join40(directory, "runs", `note-${projectKey(projectPath)}.json`);
48435
+ return join41(directory, "runs", `note-${projectKey(projectPath)}.json`);
48372
48436
  }
48373
48437
  function latestRunPath(servedBy, directory = runtimeDirectory()) {
48374
- return join40(directory, "runs", `latest-${projectKey(servedBy)}.json`);
48438
+ return join41(directory, "runs", `latest-${projectKey(servedBy)}.json`);
48375
48439
  }
48376
48440
  function writeLatestRun(servedBy, latest) {
48377
48441
  try {
@@ -48381,7 +48445,7 @@ function writeLatestRun(servedBy, latest) {
48381
48445
  }
48382
48446
  function readLatestRun(servedBy, directory = runtimeDirectory()) {
48383
48447
  try {
48384
- const fields = JSON.parse(readFileSync34(latestRunPath(servedBy, directory), "utf8"));
48448
+ const fields = JSON.parse(readFileSync35(latestRunPath(servedBy, directory), "utf8"));
48385
48449
  if (typeof fields !== "object" || fields === null) {
48386
48450
  return null;
48387
48451
  }
@@ -48396,20 +48460,20 @@ function readLatestRun(servedBy, directory = runtimeDirectory()) {
48396
48460
  }
48397
48461
  function openTranscript(startedAt) {
48398
48462
  mkdirSync21(runsDirectory(), { recursive: true });
48399
- const path = join40(runsDirectory(), `run-${startedAt}.log`);
48463
+ const path = join41(runsDirectory(), `run-${startedAt}.log`);
48400
48464
  return { path, fd: openSync5(path, "a") };
48401
48465
  }
48402
48466
  function profilePath(startedAt) {
48403
- return join40(runsDirectory(), `run-${startedAt}.profile.json`);
48467
+ return join41(runsDirectory(), `run-${startedAt}.profile.json`);
48404
48468
  }
48405
48469
  function profileMarkPath(startedAt) {
48406
- return join40(runsDirectory(), `run-${startedAt}.profile-mark.json`);
48470
+ return join41(runsDirectory(), `run-${startedAt}.profile-mark.json`);
48407
48471
  }
48408
48472
  function noteIsAbout(note, playing) {
48409
48473
  return note.play !== undefined && playing !== null && note.play.play === playing.play && note.play.editorPid === playing.editorPid;
48410
48474
  }
48411
48475
  function editorNotePath(projectPath, directory = runtimeDirectory()) {
48412
- return join40(directory, "runs", `editor-${projectKey(projectPath)}.json`);
48476
+ return join41(directory, "runs", `editor-${projectKey(projectPath)}.json`);
48413
48477
  }
48414
48478
  function writeEditorRunNote(note) {
48415
48479
  mkdirSync21(runsDirectory(), { recursive: true });
@@ -48430,7 +48494,7 @@ function readEditorRunNote(projectPath) {
48430
48494
  function editorNoteAt(path) {
48431
48495
  let parsed;
48432
48496
  try {
48433
- parsed = JSON.parse(readFileSync34(path, "utf8"));
48497
+ parsed = JSON.parse(readFileSync35(path, "utf8"));
48434
48498
  } catch {
48435
48499
  return null;
48436
48500
  }
@@ -48462,13 +48526,13 @@ function everyRunRecord() {
48462
48526
  for (const directory of runtimeDirectories()) {
48463
48527
  let entries;
48464
48528
  try {
48465
- entries = readdirSync18(join40(directory, "runs"));
48529
+ entries = readdirSync18(join41(directory, "runs"));
48466
48530
  } catch {
48467
48531
  continue;
48468
48532
  }
48469
48533
  for (const entry of entries) {
48470
48534
  if (entry.startsWith("note-") && entry.endsWith(".json")) {
48471
- const record = recordAt(join40(directory, "runs", entry));
48535
+ const record = recordAt(join41(directory, "runs", entry));
48472
48536
  if (record !== null) {
48473
48537
  found.push(record);
48474
48538
  }
@@ -48480,7 +48544,7 @@ function everyRunRecord() {
48480
48544
  function recordAt(path) {
48481
48545
  let parsed;
48482
48546
  try {
48483
- parsed = JSON.parse(readFileSync34(path, "utf8"));
48547
+ parsed = JSON.parse(readFileSync35(path, "utf8"));
48484
48548
  } catch {
48485
48549
  return null;
48486
48550
  }
@@ -48561,7 +48625,7 @@ async function runningAs(pid) {
48561
48625
  const began = await startedAt(pid, null);
48562
48626
  const when = began === null ? {} : { startedAt: began };
48563
48627
  if (process.platform === "linux") {
48564
- const raw = readFileSync34(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
48628
+ const raw = readFileSync35(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
48565
48629
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
48566
48630
  }
48567
48631
  const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
@@ -48736,7 +48800,7 @@ function sweepTranscripts(keepMs = 24 * 60 * 60 * 1000, now = Date.now()) {
48736
48800
  if (!transcript && !entry.endsWith(".partial")) {
48737
48801
  continue;
48738
48802
  }
48739
- const path = join40(directory, entry);
48803
+ const path = join41(directory, entry);
48740
48804
  if (kept.has(path) || ageMs(path, now) < keepMs) {
48741
48805
  continue;
48742
48806
  }
@@ -48757,10 +48821,10 @@ var init_run_record = __esm(() => {
48757
48821
  });
48758
48822
 
48759
48823
  // src/script-profile.ts
48760
- import { readFileSync as readFileSync35 } from "node:fs";
48824
+ import { readFileSync as readFileSync36 } from "node:fs";
48761
48825
  function readProfileFile(path) {
48762
48826
  try {
48763
- const parsed = JSON.parse(readFileSync35(path, "utf8"));
48827
+ const parsed = JSON.parse(readFileSync36(path, "utf8"));
48764
48828
  return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
48765
48829
  } catch {
48766
48830
  return null;
@@ -48818,7 +48882,7 @@ function writeProfileMark(path, mark) {
48818
48882
  }
48819
48883
  function readProfileMark(path) {
48820
48884
  try {
48821
- const parsed = JSON.parse(readFileSync35(path, "utf8"));
48885
+ const parsed = JSON.parse(readFileSync36(path, "utf8"));
48822
48886
  return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
48823
48887
  } catch {
48824
48888
  return null;
@@ -48992,16 +49056,16 @@ var init_server_instructions = __esm(() => {
48992
49056
 
48993
49057
  // src/suites-scene.ts
48994
49058
  import { existsSync as existsSync32, mkdirSync as mkdirSync22, writeFileSync as writeFileSync21 } from "node:fs";
48995
- import { join as join41, relative as relative9, sep as sep5 } from "node:path";
49059
+ import { join as join42, relative as relative9, sep as sep5 } from "node:path";
48996
49060
  function hasSuitesScene(projectPath) {
48997
- return existsSync32(join41(projectPath, SUITES_SCENE));
49061
+ return existsSync32(join42(projectPath, SUITES_SCENE));
48998
49062
  }
48999
49063
  function suitesSceneArgs(projectPath, suites) {
49000
- const runs = join41(projectPath, ".godot", "gdharness", "suite-scenes");
49064
+ const runs = join42(projectPath, ".godot", "gdharness", "suite-scenes");
49001
49065
  sweepRunDirectories(runs);
49002
- const directory = join41(runs, `${String(STARTED_AT)}-${String(process.pid)}`);
49066
+ const directory = join42(runs, `${String(STARTED_AT)}-${String(process.pid)}`);
49003
49067
  mkdirSync22(directory, { recursive: true });
49004
- const suitesFile = join41(directory, "suites.txt");
49068
+ const suitesFile = join42(directory, "suites.txt");
49005
49069
  writeFileSync21(suitesFile, `${suites.join(`
49006
49070
  `)}
49007
49071
  `);
@@ -49016,7 +49080,7 @@ function suitesSceneArgs(projectPath, suites) {
49016
49080
  "-c",
49017
49081
  "--ignoreHeadlessMode",
49018
49082
  "-rd",
49019
- `res://${relative9(projectPath, join41(directory, "reports")).split(sep5).join("/")}`,
49083
+ `res://${relative9(projectPath, join42(directory, "reports")).split(sep5).join("/")}`,
49020
49084
  "-rc",
49021
49085
  "1"
49022
49086
  ];
@@ -49030,7 +49094,7 @@ var init_suites_scene = __esm(() => {
49030
49094
 
49031
49095
  // src/test-selection.ts
49032
49096
  import { existsSync as existsSync33 } from "node:fs";
49033
- import { basename as basename12, join as join42 } from "node:path";
49097
+ import { basename as basename12, join as join43 } from "node:path";
49034
49098
  function casesOutOfPath(projectPath, given, tests) {
49035
49099
  const entries = Array.isArray(given) ? given : [given];
49036
49100
  const cases = [];
@@ -49074,12 +49138,12 @@ function casesIn(scripts, suite) {
49074
49138
  }
49075
49139
  function scriptsWithoutCases(projectPath, asked) {
49076
49140
  const scripts = new ProjectScripts(projectPath);
49077
- return asked.filter((path) => path.endsWith(".gd") && existsSync33(join42(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
49141
+ return asked.filter((path) => path.endsWith(".gd") && existsSync33(join43(projectPath, path.slice("res://".length))) && casesIn(scripts, path).length === 0);
49078
49142
  }
49079
49143
  function suitesUnder(projectPath, asked) {
49080
49144
  const scripts = projectFiles(projectPath, [".gd"]).map((path) => `res://${path}`);
49081
49145
  return [
49082
- ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync33(join42(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
49146
+ ...new Set(asked.flatMap((path) => path.endsWith(".gd") ? existsSync33(join43(projectPath, path.slice("res://".length))) ? [path] : [] : scripts.filter((script) => script.startsWith(`${path}/`))))
49083
49147
  ];
49084
49148
  }
49085
49149
  function selectTests(projectPath, asked, given) {
@@ -49150,14 +49214,14 @@ import {
49150
49214
  mkdirSync as mkdirSync23,
49151
49215
  openSync as openSync6,
49152
49216
  readdirSync as readdirSync19,
49153
- readFileSync as readFileSync36,
49217
+ readFileSync as readFileSync37,
49154
49218
  readSync as readSync3,
49155
49219
  realpathSync as realpathSync5,
49156
49220
  rmdirSync as rmdirSync2,
49157
49221
  statSync as statSync20,
49158
49222
  writeFileSync as writeFileSync22
49159
49223
  } from "node:fs";
49160
- import { basename as basename13, dirname as dirname25, extname as extname4, isAbsolute as isAbsolute8, join as join43, normalize as normalize2, relative as relative10 } from "node:path";
49224
+ import { basename as basename13, dirname as dirname25, extname as extname4, isAbsolute as isAbsolute8, join as join44, normalize as normalize2, relative as relative10 } from "node:path";
49161
49225
  import { setTimeout as delay7 } from "node:timers/promises";
49162
49226
  import { fileURLToPath as fileURLToPath5 } from "node:url";
49163
49227
  function editorRestartTimeoutMs() {
@@ -49171,9 +49235,9 @@ function greetingWaitMs() {
49171
49235
  return waitFromEnv("GDHARNESS_GREETING_WAIT_MS", 1e4);
49172
49236
  }
49173
49237
  function pictureToSend(pngPath, jpegPath) {
49174
- const png = readFileSync36(pngPath);
49238
+ const png = readFileSync37(pngPath);
49175
49239
  const beside = pngPath.replace(/\.png$/, ".jpg");
49176
- const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ? readFileSync36(beside) : null;
49240
+ const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ? readFileSync37(beside) : null;
49177
49241
  const sent = jpeg !== null && jpeg.length < png.length ? jpeg : png;
49178
49242
  return {
49179
49243
  data: sent.toString("base64"),
@@ -49216,7 +49280,7 @@ function writtenFileRefusal(path, rule) {
49216
49280
  if (!existsSync34(directory)) {
49217
49281
  return `${named}'s directory ${dirname25(path)} does not exist; ${what} creates no directories.`;
49218
49282
  }
49219
- const where = join43(directory, basename13(path));
49283
+ const where = join44(directory, basename13(path));
49220
49284
  const inside = rule.projects.find((project) => project !== "" && isWithinRoot(realAsFarAsItExists(project), where));
49221
49285
  if (inside !== undefined) {
49222
49286
  return `${named} ${path} is inside the project at ${inside}; ${what} is saved outside it, so it never becomes part of the game.`;
@@ -49652,7 +49716,7 @@ function endedToStartThis(ended) {
49652
49716
  return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
49653
49717
  }
49654
49718
  function lineNothingStopsOn(absolutePath, relativePath, line) {
49655
- const text = readFileSync36(absolutePath, "utf8");
49719
+ const text = readFileSync37(absolutePath, "utf8");
49656
49720
  const lines = text === "" ? 0 : text.split(`
49657
49721
  `).length - (text.endsWith(`
49658
49722
  `) ? 1 : 0);
@@ -49863,7 +49927,7 @@ function notRunNote(notRun, failFast) {
49863
49927
  function presetsIn(projectPath) {
49864
49928
  let text;
49865
49929
  try {
49866
- text = readFileSync36(join43(projectPath, "export_presets.cfg"), "utf8");
49930
+ text = readFileSync37(join44(projectPath, "export_presets.cfg"), "utf8");
49867
49931
  } catch {
49868
49932
  return null;
49869
49933
  }
@@ -49944,7 +50008,7 @@ function filesIn(folder) {
49944
50008
  try {
49945
50009
  for (const entry of readdirSync19(folder, { withFileTypes: true })) {
49946
50010
  if (entry.isFile()) {
49947
- found.set(entry.name, statSync20(join43(folder, entry.name)).mtimeMs);
50011
+ found.set(entry.name, statSync20(join44(folder, entry.name)).mtimeMs);
49948
50012
  }
49949
50013
  }
49950
50014
  } catch {}
@@ -49984,7 +50048,7 @@ function unsavedInEditorNote(scripts) {
49984
50048
  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.`;
49985
50049
  }
49986
50050
  function editorAddonMissing(projectPath) {
49987
- if (!existsSync34(join43(projectPath, "project.godot"))) {
50051
+ if (!existsSync34(join44(projectPath, "project.godot"))) {
49988
50052
  return null;
49989
50053
  }
49990
50054
  const report = inspectProject(projectPath);
@@ -50012,8 +50076,8 @@ function rewriteBusNamed(projectPath, from, to) {
50012
50076
  const giving = [`bus = &"${quoted(from)}"`, `bus = "${quoted(from)}"`];
50013
50077
  const rewritten = [];
50014
50078
  for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
50015
- const absolute = join43(projectPath, file);
50016
- const lines = readFileSync36(absolute, "utf8").split(`
50079
+ const absolute = join44(projectPath, file);
50080
+ const lines = readFileSync37(absolute, "utf8").split(`
50017
50081
  `);
50018
50082
  const at = [];
50019
50083
  const after = lines.map((line, index) => {
@@ -50032,7 +50096,7 @@ function rewriteBusNamed(projectPath, from, to) {
50032
50096
  }
50033
50097
  const namedInScripts = [];
50034
50098
  for (const file of projectFiles(projectPath, [".gd"])) {
50035
- readFileSync36(join43(projectPath, file), "utf8").split(`
50099
+ readFileSync37(join44(projectPath, file), "utf8").split(`
50036
50100
  `).forEach((line, index) => {
50037
50101
  if (line.includes(`"${from}"`)) {
50038
50102
  namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
@@ -50317,7 +50381,7 @@ function profileFrameFunctions(projectPath) {
50317
50381
  let projectGodot = null;
50318
50382
  if (projectPath !== null) {
50319
50383
  try {
50320
- projectGodot = readFileSync36(join43(projectPath, "project.godot"), "utf8");
50384
+ projectGodot = readFileSync37(join44(projectPath, "project.godot"), "utf8");
50321
50385
  } catch {}
50322
50386
  }
50323
50387
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -50341,7 +50405,7 @@ function projectSourceOf(projectPath) {
50341
50405
  let source = null;
50342
50406
  if (inside.ok) {
50343
50407
  try {
50344
- source = readFileSync36(inside.absolutePath, "utf8");
50408
+ source = readFileSync37(inside.absolutePath, "utf8");
50345
50409
  } catch {}
50346
50410
  }
50347
50411
  read.set(script, source);
@@ -50513,7 +50577,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
50513
50577
  }
50514
50578
  function gdUnitFailsOnScriptErrors(projectPath) {
50515
50579
  try {
50516
- return settingKeys(readFileSync36(join43(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
50580
+ return settingKeys(readFileSync37(join44(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
50517
50581
  } catch {
50518
50582
  return true;
50519
50583
  }
@@ -50528,7 +50592,7 @@ function uncaughtNote(uncaught) {
50528
50592
  }
50529
50593
  function elsewhereIn(projectPath, asked) {
50530
50594
  const wanted = asked.replace(/^res:\/\//, "").replace(/\/+$/, "");
50531
- if (wanted === "" || existsSync34(join43(projectPath, wanted))) {
50595
+ if (wanted === "" || existsSync34(join44(projectPath, wanted))) {
50532
50596
  return "";
50533
50597
  }
50534
50598
  const root = (wanted.split("/")[0] ?? "").toLowerCase();
@@ -50551,7 +50615,7 @@ function windowNoteFor(needing, sharing) {
50551
50615
  return `${named(needing)} ${one ? "needs" : "need"} a window, as gdharness test reads ${one ? "it" : "them"}, so the run had one: the engine ran without --headless, at the size project.godot gives its viewport, with no sound${process.platform === "win32" ? ", on a desktop of its own, where its window neither shows nor takes the keyboard" : ""}. ${why}${shared}`;
50552
50616
  }
50553
50617
  function testPathsIn(projectPath, given, named = "path") {
50554
- const defaults = TEST_DIRECTORIES.filter((directory) => existsSync34(join43(projectPath, directory)));
50618
+ const defaults = TEST_DIRECTORIES.filter((directory) => existsSync34(join44(projectPath, directory)));
50555
50619
  const entries = given === undefined || given === null || given === "" ? defaults.length > 0 ? defaults : TEST_DIRECTORIES : given;
50556
50620
  const listed = Array.isArray(entries) ? entries : [entries];
50557
50621
  if (listed.length === 0) {
@@ -50645,7 +50709,7 @@ function engineChecked(why) {
50645
50709
  return {
50646
50710
  checkedBy: "engine",
50647
50711
  languageServer: why,
50648
- note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, but stops reading a script at its first syntax error, so an error after one shows once it is fixed. A second engine reports the warnings, at the levels the project sets, by raising each warning set to warn as an error for that run only, since the engine tells a warning as one only to an editor; each comes at its line without a column, and warnings is null when that engine could not answer. editor_launch with hidden: true opens this project's editor without a window, and its language server reports both with their columns."
50712
+ note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, but stops reading a script at its first syntax error, so an error after one shows once it is fixed, and so do its warnings, since a script that does not parse is not checked for any. A second engine reports the warnings, at the levels the project sets, by raising each warning set to warn as an error for that run only, since the engine tells a warning as one only to an editor; each comes at its line without a column, and warnings is null when that engine could not answer. editor_launch with hidden: true opens this project's editor without a window, and its language server reports both with their columns."
50649
50713
  };
50650
50714
  }
50651
50715
  function asWarning(raised) {
@@ -50660,11 +50724,11 @@ function isDirectory(path) {
50660
50724
  }
50661
50725
  }
50662
50726
  function mainSceneOf(projectFile) {
50663
- const scene = parseProjectGodot(readFileSync36(projectFile, "utf8"))["application"]?.["run/main_scene"];
50727
+ const scene = parseProjectGodot(readFileSync37(projectFile, "utf8"))["application"]?.["run/main_scene"];
50664
50728
  return typeof scene === "string" && scene !== "" ? scene : null;
50665
50729
  }
50666
50730
  function editorPlaysHeadless(projectFile) {
50667
- const runArgs = parseProjectGodot(readFileSync36(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
50731
+ const runArgs = parseProjectGodot(readFileSync37(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
50668
50732
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
50669
50733
  }
50670
50734
  function ownArguments(params) {
@@ -50682,7 +50746,7 @@ function needsItsOwnProcess(asked) {
50682
50746
  class GodotServer {
50683
50747
  stdio = null;
50684
50748
  locator = new GodotLocator;
50685
- operationsScript = join43(__dirname2, "godot", "operations", "godot_operations.gd");
50749
+ operationsScript = join44(__dirname2, "godot", "operations", "godot_operations.gd");
50686
50750
  exportOptionsRead = new Map;
50687
50751
  heldWhilePending = new Set;
50688
50752
  evaluatedWhilePending = new Set;
@@ -50954,7 +51018,7 @@ class GodotServer {
50954
51018
  }
50955
51019
  noteTheStart() {
50956
51020
  const cwd = process.cwd();
50957
- const mcpJson = join43(cwd, ".mcp.json");
51021
+ const mcpJson = join44(cwd, ".mcp.json");
50958
51022
  const announced = this.ownProject === null ? null : readAnnouncement(announcementPath(this.ownProject));
50959
51023
  const predecessor = announced !== null && announced.pid !== process.pid && alive(announced.pid) ? announced.pid : null;
50960
51024
  writeLifecycle("started", {
@@ -51102,7 +51166,7 @@ class GodotServer {
51102
51166
  return checked.response;
51103
51167
  }
51104
51168
  const named = args["projectPath"];
51105
- if (typeof named === "string" && existsSync34(join43(named, "project.godot"))) {
51169
+ if (typeof named === "string" && existsSync34(join44(named, "project.godot"))) {
51106
51170
  this.lastProjectPath = named;
51107
51171
  }
51108
51172
  this.updates.refresh();
@@ -51117,12 +51181,16 @@ class GodotServer {
51117
51181
  };
51118
51182
  const call = this.callsTaken++;
51119
51183
  this.inFlight.set(call, { tool: spec.name, op: checked.op ?? "", startedAt: Date.now() });
51184
+ const slotWaits = { ms: 0, said: undefined };
51120
51185
  try {
51121
51186
  const answer = await withCallSignal(ctx.mcpReq.signal, () => this.answered(spec.name, checked.op ?? "", args), progress, {
51122
51187
  everyMs: CALL_HEARTBEAT_MS,
51123
51188
  said: (elapsedMs) => stillGoingSaid(spec.name, checked.op, elapsedMs)
51189
+ }, slotWaits);
51190
+ const waited = slotWaits.said === undefined || slotWaits.ms < 1000 ? answer : this.saying(answer, {
51191
+ engine_slot_wait: { waitedMs: slotWaits.ms, note: slotWaits.said(slotWaits.ms) }
51124
51192
  });
51125
- return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(answer)));
51193
+ return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(waited)));
51126
51194
  } finally {
51127
51195
  this.inFlight.delete(call);
51128
51196
  }
@@ -51141,7 +51209,7 @@ class GodotServer {
51141
51209
  let notSaved;
51142
51210
  try {
51143
51211
  savedAt = answer.content.map((item, index) => {
51144
- const file = join43(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
51212
+ const file = join44(directory, item.type === "text" ? `${stem}-${index}.txt` : `${stem}-${index}.${item.mimeType.split("/")[1] ?? "bin"}`);
51145
51213
  writeFileSync22(file, item.type === "text" ? item.text : Buffer.from(item.data, "base64"));
51146
51214
  return file;
51147
51215
  });
@@ -51801,6 +51869,10 @@ class GodotServer {
51801
51869
  if (kept !== null) {
51802
51870
  return await this.askTheKeptGame(kept.keeper, keptGameRequest(tool, op, args), kept.projectPath ?? "");
51803
51871
  }
51872
+ const last = this.activeProcess;
51873
+ if (tool === "debug_state" && op === "output" && last !== null && !last.throughEditor && last.keeper !== undefined) {
51874
+ return this.createErrorResponse("The game this server started has ended, and its debugger, which held its console output, ended with it.", ["editor_output reads what the game printed, with the errors and warnings it reported"]);
51875
+ }
51804
51876
  if (tool === "debug_state" && op === "output") {
51805
51877
  return await this.handleDAP("dap_get_output", args);
51806
51878
  }
@@ -51834,7 +51906,7 @@ class GodotServer {
51834
51906
  if (path === undefined) {
51835
51907
  return { ok: false, response: this.createErrorResponse("projectPath is required.") };
51836
51908
  }
51837
- const file = join43(path, "project.godot");
51909
+ const file = join44(path, "project.godot");
51838
51910
  if (!existsSync34(file)) {
51839
51911
  return {
51840
51912
  ok: false,
@@ -52579,7 +52651,7 @@ class GodotServer {
52579
52651
  if (!engine.ok) {
52580
52652
  return engine.response;
52581
52653
  }
52582
- const application = parseProjectGodot(readFileSync36(project.value.file, "utf8"))["application"] ?? {};
52654
+ const application = parseProjectGodot(readFileSync37(project.value.file, "utf8"))["application"] ?? {};
52583
52655
  const name = application["config/name"];
52584
52656
  const mainScene = application["run/main_scene"];
52585
52657
  const info = {
@@ -52716,7 +52788,7 @@ class GodotServer {
52716
52788
  const platform = presetPlatform(project.value.path, preset);
52717
52789
  if (platform !== null) {
52718
52790
  const known = await this.exportOptions(platform, {
52719
- presetsFile: join43(project.value.path, "export_presets.cfg"),
52791
+ presetsFile: join44(project.value.path, "export_presets.cfg"),
52720
52792
  preset,
52721
52793
  asked: options
52722
52794
  });
@@ -52767,7 +52839,7 @@ ${platform}`;
52767
52839
  }
52768
52840
  const empty = scratchDirectory("export");
52769
52841
  try {
52770
- writeFileSync22(join43(empty, "project.godot"), `config_version=5
52842
+ writeFileSync22(join44(empty, "project.godot"), `config_version=5
52771
52843
  `);
52772
52844
  const outcome = await runOperation({ godotPath: engine.value, script: this.operationsScript, debug: DEBUG_MODE, editor: true }, "export_options", { platform, ...standing }, empty);
52773
52845
  const options = outcome.ok ? outcome.payload["options"] : undefined;
@@ -52823,7 +52895,7 @@ ${platform}`;
52823
52895
  const exportArgs = [
52824
52896
  "--headless",
52825
52897
  "--log-file",
52826
- join43(exportLogs, "engine.log"),
52898
+ join44(exportLogs, "engine.log"),
52827
52899
  "--path",
52828
52900
  project.value.path,
52829
52901
  pack ? "--export-pack" : debug ? "--export-debug" : "--export-release",
@@ -52922,7 +52994,7 @@ ${platform}`;
52922
52994
  if (movie !== null) {
52923
52995
  return this.createErrorResponse(movie);
52924
52996
  }
52925
- const missing = paths.value.filter((path) => !existsSync34(join43(project.value.path, path.slice("res://".length))));
52997
+ const missing = paths.value.filter((path) => !existsSync34(join44(project.value.path, path.slice("res://".length))));
52926
52998
  const projectScripts = new ProjectScripts(project.value.path);
52927
52999
  const present = paths.value.filter((path) => !missing.includes(path));
52928
53000
  const asked = projectScripts.searchedAsTold(present);
@@ -53014,10 +53086,10 @@ ${platform}`;
53014
53086
  const ignored = readStringArray(args, "ignore") ?? [];
53015
53087
  const declaredUnder = ignored.length === 0 ? [] : asked.flatMap((path) => path.endsWith(".gd") ? projectScripts.suite(path) ?? [] : projectScripts.suitesUnder([path]));
53016
53088
  const userData = scratchDirectory("tests");
53017
- const suitesFile = join43(userData, "suites.txt");
53018
- const skippingFile = join43(userData, "skipping.txt");
53019
- const engineLog = join43(userData, "engine.log");
53020
- const expectedFile = join43(userData, "expected.json");
53089
+ const suitesFile = join44(userData, "suites.txt");
53090
+ const skippingFile = join44(userData, "skipping.txt");
53091
+ const engineLog = join44(userData, "engine.log");
53092
+ const expectedFile = join44(userData, "expected.json");
53021
53093
  writeFileSync22(suitesFile, `${(selection.value?.suites ?? asked).join(`
53022
53094
  `)}
53023
53095
  `);
@@ -53058,11 +53130,11 @@ ${platform}`;
53058
53130
  const timeoutMs = readPositiveNumber(args, "timeoutMs") ?? TEST_TIMEOUT_MS;
53059
53131
  this.logDebug(`Running tests: ${engine.value} ${cmdArgs.join(" ")}`);
53060
53132
  const helperFiles = {
53061
- spec: join43(userData, "spec"),
53062
- pid: join43(userData, "pid"),
53063
- error: join43(userData, "error"),
53064
- stop: join43(userData, "stop"),
53065
- step: join43(userData, "step")
53133
+ spec: join44(userData, "spec"),
53134
+ pid: join44(userData, "pid"),
53135
+ error: join44(userData, "error"),
53136
+ stop: join44(userData, "stop"),
53137
+ step: join44(userData, "step")
53066
53138
  };
53067
53139
  const onDesktop = windowed && process.platform === "win32";
53068
53140
  const run = await this.spawnGame(engine.value, cmdArgs, userDataIn(userData), onDesktop ? { directory: project.value.path, files: helperFiles } : undefined);
@@ -53116,7 +53188,7 @@ ${platform}`;
53116
53188
  await run.settled;
53117
53189
  const helperFailed = onDesktop ? helperFailure(helperFiles.error) ?? (existsSync34(helperFiles.pid) ? null : `it never started, and ${helperStep(helperFiles.step)}`) : null;
53118
53190
  if (helperFailed !== null) {
53119
- discard(join43(project.value.path, ".godot", ...ours.split("/")), userData);
53191
+ discard(join44(project.value.path, ".godot", ...ours.split("/")), userData);
53120
53192
  return this.createErrorResponse(`No tests ran: ${named(needing)} ${needing.length === 1 ? "needs" : "need"} a window, and the engine could not be started on a desktop of its own: ${helperFailed}`);
53121
53193
  }
53122
53194
  const endedAt = Date.now();
@@ -53127,16 +53199,16 @@ ${platform}`;
53127
53199
  engine: endedAt - importedAt
53128
53200
  };
53129
53201
  const notCompiled = stopped.for ?? run.log.all.map((entry) => autoloadNotCompiled(entry, autoloaded)).find((found) => found !== null) ?? null;
53130
- const reportsDir = join43(project.value.path, ".godot", ...ours.split("/"));
53202
+ const reportsDir = join44(project.value.path, ".godot", ...ours.split("/"));
53131
53203
  let report = null;
53132
53204
  let reportProblem = null;
53133
- const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync36(engineLog, "utf8") : ""), run.log.all);
53205
+ const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync37(engineLog, "utf8") : ""), run.log.all);
53134
53206
  const declaredErrors = readExpected(expectedFile);
53135
53207
  try {
53136
53208
  const written = existsSync34(reportsDir) && notCompiled === null ? readdirSync19(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
53137
53209
  const newest = written.at(-1);
53138
53210
  if (newest !== undefined) {
53139
- report = parseJUnit(readFileSync36(join43(reportsDir, newest, "results.xml"), "utf8"));
53211
+ report = parseJUnit(readFileSync37(join44(reportsDir, newest, "results.xml"), "utf8"));
53140
53212
  }
53141
53213
  } catch (error) {
53142
53214
  reportProblem = errorMessage(error);
@@ -53541,7 +53613,7 @@ ${error.text}`;
53541
53613
  const scripts = projectFiles(projectPath, [".gd"]).map((script) => `res://${script}`);
53542
53614
  const wanted = `res://${relativePath}`;
53543
53615
  const folder = dirname25(relativePath.replaceAll("\\", "/"));
53544
- const inFolder = join43(projectPath, folder);
53616
+ const inFolder = join44(projectPath, folder);
53545
53617
  const beside = existsSync34(inFolder) ? readdirSync19(inFolder, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => `res://${folder === "." ? "" : `${folder}/`}${entry.name}`) : [];
53546
53618
  return this.createErrorResponse(withNearestFiles(`Script file does not exist: ${wanted}`, wanted, scripts, beside));
53547
53619
  }
@@ -53674,7 +53746,7 @@ ${error.text}`;
53674
53746
  return null;
53675
53747
  }
53676
53748
  try {
53677
- return readFileSync36(contained.absolutePath, "utf8");
53749
+ return readFileSync37(contained.absolutePath, "utf8");
53678
53750
  } catch {
53679
53751
  return null;
53680
53752
  }
@@ -53768,7 +53840,7 @@ ${error.text}`;
53768
53840
  return null;
53769
53841
  }
53770
53842
  try {
53771
- return readFileSync36(contained.absolutePath, "utf8");
53843
+ return readFileSync37(contained.absolutePath, "utf8");
53772
53844
  } catch {
53773
53845
  return null;
53774
53846
  }
@@ -53942,7 +54014,7 @@ ${error.text}`;
53942
54014
  ...Array.isArray(stack) ? {
53943
54015
  stack: stack.map((frame) => ({
53944
54016
  ...frame,
53945
- ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join43(projectPath, frame["script"].slice("res://".length)) } : {}
54017
+ ...projectPath !== "" && typeof frame["script"] === "string" && frame["script"].startsWith("res://") ? { path: join44(projectPath, frame["script"].slice("res://".length)) } : {}
53946
54018
  }))
53947
54019
  } : {}
53948
54020
  });
@@ -54287,7 +54359,7 @@ ${error.text}`;
54287
54359
  return null;
54288
54360
  }
54289
54361
  try {
54290
- return settingKeys(readFileSync36(join43(projectPath, "project.godot"), "utf8"));
54362
+ return settingKeys(readFileSync37(join44(projectPath, "project.godot"), "utf8"));
54291
54363
  } catch {
54292
54364
  return null;
54293
54365
  }
@@ -54632,7 +54704,7 @@ ${error.text}`;
54632
54704
  if (!paths.ok) {
54633
54705
  return refused(paths.reason, PATH_SOLUTIONS);
54634
54706
  }
54635
- const missing = paths.value.filter((path) => !existsSync34(join43(projectPath, path.slice("res://".length))));
54707
+ const missing = paths.value.filter((path) => !existsSync34(join44(projectPath, path.slice("res://".length))));
54636
54708
  if (missing.length > 0) {
54637
54709
  return refused(`Nothing at ${missing.join(" and ")} in the project at ${projectPath}.`, PATH_SOLUTIONS);
54638
54710
  }
@@ -54803,7 +54875,7 @@ ${error.text}`;
54803
54875
  }
54804
54876
  async runtimeUp(projectPath, before, budget, withArgs) {
54805
54877
  const budgetMs = budget.ms;
54806
- if (!existsSync34(join43(projectPath, RUNTIME_SCRIPT))) {
54878
+ if (!existsSync34(join44(projectPath, RUNTIME_SCRIPT))) {
54807
54879
  return runtimeVerdict(null, {
54808
54880
  addon: false,
54809
54881
  budgetMs,
@@ -55112,7 +55184,7 @@ ${error.text}`;
55112
55184
  return 0;
55113
55185
  }
55114
55186
  try {
55115
- return reportAlreadyInTranscript(readFileSync36(game.transcript), readFileSync36(report));
55187
+ return reportAlreadyInTranscript(readFileSync37(game.transcript), readFileSync37(report));
55116
55188
  } catch (error) {
55117
55189
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
55118
55190
  return 0;
@@ -55845,7 +55917,7 @@ ${error.text}`;
55845
55917
  };
55846
55918
  const going = await this.runStillGoing(run);
55847
55919
  const projectPath = run.projectPath;
55848
- const projectGone = projectPath !== null && !existsSync34(join43(projectPath, "project.godot"));
55920
+ const projectGone = projectPath !== null && !existsSync34(join44(projectPath, "project.godot"));
55849
55921
  if (projectGone && callersName !== undefined) {
55850
55922
  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"]);
55851
55923
  }
@@ -56230,7 +56302,7 @@ ${error.text}`;
56230
56302
  notes.push(endedWithoutACode({
56231
56303
  throughEditor: run.throughEditor,
56232
56304
  announced: announced !== undefined || run.announcedPid !== undefined,
56233
- couldAnnounce: run.projectPath !== null && existsSync34(join43(run.projectPath, RUNTIME_SCRIPT)),
56305
+ couldAnnounce: run.projectPath !== null && existsSync34(join44(run.projectPath, RUNTIME_SCRIPT)),
56234
56306
  endedHere: typeof run.endedHere === "string",
56235
56307
  pickedUp: run.pickedUp === true
56236
56308
  }));
@@ -56496,6 +56568,8 @@ ${error.text}`;
56496
56568
  let writtenBefore = null;
56497
56569
  let blind = { unseen: [] };
56498
56570
  let waitedForEditorMs = 0;
56571
+ let answerStalledMs = 0;
56572
+ const deadline = () => started + timeoutMs + answerStalledMs;
56499
56573
  let startUnanswered = false;
56500
56574
  let completedBefore;
56501
56575
  let uidsReread = [];
@@ -56503,7 +56577,15 @@ ${error.text}`;
56503
56577
  for (;; ) {
56504
56578
  const busySince = Date.now();
56505
56579
  let wasBusy = false;
56506
- while (Date.now() - started < timeoutMs && await this.editorIsScanningOrImporting()) {
56580
+ while (Date.now() < deadline()) {
56581
+ const asked = Date.now();
56582
+ if (!await this.editorIsScanningOrImporting()) {
56583
+ const took = Date.now() - asked;
56584
+ if (took >= SLOW_ANSWER_MS) {
56585
+ answerStalledMs += took;
56586
+ }
56587
+ break;
56588
+ }
56507
56589
  wasBusy = true;
56508
56590
  await new Promise((settle) => setTimeout(settle, 100));
56509
56591
  }
@@ -56528,7 +56610,7 @@ ${error.text}`;
56528
56610
  if (duplicated !== undefined) {
56529
56611
  uidsDuplicatedOnDisk = duplicated.map((group) => Array.isArray(group) ? group.map(String) : []);
56530
56612
  }
56531
- if (firstAnswer["started"] !== false || Date.now() - started >= timeoutMs) {
56613
+ if (firstAnswer["started"] !== false || Date.now() >= deadline()) {
56532
56614
  break;
56533
56615
  }
56534
56616
  await new Promise((settle) => setTimeout(settle, 100));
@@ -56538,7 +56620,9 @@ ${error.text}`;
56538
56620
  const ghosts = blind.stillHeld ?? [];
56539
56621
  let busy = true;
56540
56622
  let idleSince = null;
56541
- while (Date.now() - started < timeoutMs) {
56623
+ let looked = false;
56624
+ while (!looked || Date.now() < deadline()) {
56625
+ looked = true;
56542
56626
  await new Promise((settle) => setTimeout(settle, 100));
56543
56627
  let status;
56544
56628
  try {
@@ -56584,6 +56668,7 @@ ${error.text}`;
56584
56668
  let reloaded = {};
56585
56669
  const reloadedFirst = [];
56586
56670
  let builtOn;
56671
+ let builtOnUnchecked = false;
56587
56672
  const reloadingSource = reloading === undefined || projectPath === "" ? null : this.sourceOfClass(projectPath, reloading);
56588
56673
  if (reloading !== undefined && reloadingSource !== null && isToolScript(reloadingSource)) {
56589
56674
  reloaded = {
@@ -56601,6 +56686,7 @@ ${error.text}`;
56601
56686
  answer = await reloadAsked();
56602
56687
  }
56603
56688
  builtOn = first.said;
56689
+ builtOnUnchecked = first.unchecked === true;
56604
56690
  }
56605
56691
  const methods = readArray(answer, "methods");
56606
56692
  const listed = (field) => {
@@ -56627,7 +56713,8 @@ ${error.text}`;
56627
56713
  names: projectPath === "" ? [] : this.classesAScriptNames(projectPath, script),
56628
56714
  restored: answer["restored"] === true,
56629
56715
  restoredAs: readString(answer, "restoredAs") ?? "",
56630
- ...builtOn === undefined ? {} : { builtOn }
56716
+ ...builtOn === undefined ? {} : { builtOn },
56717
+ ...builtOnUnchecked ? { builtOnUnchecked } : {}
56631
56718
  })
56632
56719
  };
56633
56720
  } else {
@@ -56655,6 +56742,9 @@ ${error.text}`;
56655
56742
  const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
56656
56743
  const stillGone = lost.filter((name) => !restored.includes(name));
56657
56744
  const notes = [];
56745
+ if (answerStalledMs > 0) {
56746
+ notes.push(`The editor took ${(answerStalledMs / 1000).toFixed(1)} seconds to say it was idle, busy on the thread that answers with an earlier request, and that time was added to the wait rather than taken from it.`);
56747
+ }
56658
56748
  if (busy) {
56659
56749
  notes.push(startUnanswered ? "The editor took the rescan in and did not answer it before the wait ran out, which is an editor importing: a hidden editor imports on the thread that answers, and answers nothing until it is done. It carries the scan out once it is free, so new files may not be visible yet." : "The editor was still scanning or importing when the wait ran out, so new files may not be visible yet.");
56660
56750
  if (unseenBefore.length > 0) {
@@ -56683,6 +56773,7 @@ ${error.text}`;
56683
56773
  stillWorking: busy,
56684
56774
  waitedMs: Date.now() - started,
56685
56775
  waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
56776
+ waitedForEditorAnswerMs: answerStalledMs > 0 ? answerStalledMs : undefined,
56686
56777
  reloadedAs: reloaded.as,
56687
56778
  ...reloadDifference(reloaded),
56688
56779
  ...args["listMembers"] === true ? {
@@ -56800,17 +56891,20 @@ ${error.text}`;
56800
56891
  try {
56801
56892
  const answer = asParams(await this.godotBridge.invokeTool("scripts_behind", { scriptPaths }));
56802
56893
  const behind = readArray(answer, "behind");
56803
- return behind === undefined ? null : { behind: behind.map(String), unsaved: (readArray(answer, "unsavedInEditor") ?? []).map(String) };
56894
+ return behind === undefined ? { unchecked: "the editor answered without a list of them" } : { behind: behind.map(String), unsaved: (readArray(answer, "unsavedInEditor") ?? []).map(String) };
56804
56895
  } catch (error) {
56805
- this.logDebug(`The editor would not say which scripts it holds behind their files: ${errorMessage(error)}`);
56806
- return null;
56896
+ return { unchecked: `the editor would not say: ${errorMessage(error)}` };
56807
56897
  }
56808
56898
  }
56809
56899
  async reloadWhatItIsBuiltOn(projectPath, scriptPath) {
56810
56900
  const against = this.scriptsCompiledAgainst(projectPath, scriptPath);
56811
56901
  const held = await this.scriptsHeldBehind(against);
56812
- if (held === null) {
56813
- return { reloaded: [] };
56902
+ if ("unchecked" in held) {
56903
+ return {
56904
+ reloaded: [],
56905
+ said: `Whether the editor holds the scripts it is compiled against behind their files could not be checked: ${held.unchecked}.`,
56906
+ unchecked: true
56907
+ };
56814
56908
  }
56815
56909
  const reloadedFor = scriptPath.startsWith("res://") ? scriptPath : `res://${scriptPath.replace(/^\/+/, "")}`;
56816
56910
  const reloaded = [];
@@ -57460,10 +57554,11 @@ ${error.text}`;
57460
57554
  const checked = await this.classesTheEditorCannotSee(args);
57461
57555
  const stillHeld = checked.stillHeld ?? [];
57462
57556
  const projectPath = typeof args["projectPath"] === "string" ? args["projectPath"] : "";
57463
- const listed = projectPath === "" ? null : cachedClasses(projectPath);
57557
+ const listed = projectPath === "" || checked.unchecked !== undefined ? null : cachedClasses(projectPath);
57464
57558
  const held = listed === null ? null : await this.scriptsHeldBehind([...new Set(listed.values())]);
57465
- const behind = listed === null || held === null ? [] : [...listed].filter(([, path]) => held.behind.includes(path)).map(([className, scriptPath]) => ({ className, scriptPath }));
57466
- if (checked.unseen.length === 0 && stillHeld.length === 0 && checked.unchecked === undefined && behind.length === 0) {
57559
+ const behindUnchecked = held !== null && "unchecked" in held ? held.unchecked : undefined;
57560
+ const behind = listed === null || held === null || "unchecked" in held ? [] : [...listed].filter(([, path]) => held.behind.includes(path)).map(([className, scriptPath]) => ({ className, scriptPath }));
57561
+ if (checked.unseen.length === 0 && stillHeld.length === 0 && checked.unchecked === undefined && behindUnchecked === undefined && behind.length === 0) {
57467
57562
  return answered;
57468
57563
  }
57469
57564
  const payload = asParams(JSON.parse(first.text));
@@ -57486,6 +57581,7 @@ ${error.text}`;
57486
57581
  stillHeldByEditor: stillHeld.length > 0 ? stillHeld : undefined,
57487
57582
  heldBehindInEditor: behind.length > 0 ? behind : undefined,
57488
57583
  classesUnchecked: checked.unchecked,
57584
+ heldBehindUnchecked: behindUnchecked,
57489
57585
  note: notes.length > 0 ? notes.join(" ") : undefined
57490
57586
  });
57491
57587
  }
@@ -57495,7 +57591,7 @@ ${error.text}`;
57495
57591
  }
57496
57592
  const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57497
57593
  const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
57498
- if (declared.test(readFileSync36(scriptPath, "utf8"))) {
57594
+ if (declared.test(readFileSync37(scriptPath, "utf8"))) {
57499
57595
  return null;
57500
57596
  }
57501
57597
  const checked = await this.classesTheEditorCannotSee(args);
@@ -57692,7 +57788,7 @@ ${error.text}`;
57692
57788
  async ownRunOnItsWay(projectPath, budgetMs) {
57693
57789
  const run = await this.currentRun();
57694
57790
  const project = projectPath ?? run?.projectPath ?? null;
57695
- if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync34(join43(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
57791
+ if (run === null || project === null || run.announcedPid !== undefined || run.projectPath !== null && !isSameDirectory(run.projectPath, project) || !existsSync34(join44(project, RUNTIME_SCRIPT)) || !await this.runStillGoing(run)) {
57696
57792
  return null;
57697
57793
  }
57698
57794
  const already = runtimesAnnounced().running;
@@ -57802,7 +57898,7 @@ ${error.text}`;
57802
57898
  return this.createErrorResponse(staleNote);
57803
57899
  }
57804
57900
  const screenshotDir = expectsScreenshot ? scratchDirectory("runtime-screenshot") : null;
57805
- const screenshotPath = screenshotDir ? join43(screenshotDir, "capture.png") : null;
57901
+ const screenshotPath = screenshotDir ? join44(screenshotDir, "capture.png") : null;
57806
57902
  const refused = (message) => this.createErrorResponse(message);
57807
57903
  const onGame = {
57808
57904
  pid: choice.endpoint.pid,
@@ -58006,7 +58102,7 @@ async function runGodotServer() {
58006
58102
  const server = new GodotServer;
58007
58103
  await server.run();
58008
58104
  }
58009
- 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, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, 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_SETTLED_MS = 500, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_TIMEOUT_MS = 600000, 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, DESTINATION_ARGUMENTS, 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, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
58105
+ 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, SUCCESSOR_SOUGHT_WITHIN_MS = 20000, 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_SETTLED_MS = 500, SLOW_ANSWER_MS = 1000, REIMPORT_TIMEOUT_MS = 300000, OPS_ELSEWHERE, AUDIO_BUS_OPS, STALLED_AFTER_MS = 30000, TEST_TIMEOUT_MS = 600000, 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, DESTINATION_ARGUMENTS, 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, MOST_PRINTED_LINES = 50, MOST_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", RAISED_WARNING = " (Warning treated as error.)", PARSE_ERROR2 = "Parse Error: ", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
58010
58106
  var init_server = __esm(() => {
58011
58107
  init_dist2();
58012
58108
  init_stdio();
@@ -58141,7 +58237,7 @@ init_bridge_announce();
58141
58237
  init_engine_launch();
58142
58238
  init_engine_slots();
58143
58239
  import { existsSync as existsSync35 } from "node:fs";
58144
- import { dirname as dirname26, join as join44, resolve as resolve14 } from "node:path";
58240
+ import { dirname as dirname26, join as join45, resolve as resolve14 } from "node:path";
58145
58241
 
58146
58242
  // src/error-mode.ts
58147
58243
  import process8 from "node:process";
@@ -58179,15 +58275,15 @@ init_alive();
58179
58275
  init_engine_launch();
58180
58276
  init_lsp_client();
58181
58277
  init_process_children();
58182
- import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync15, unlinkSync as unlinkSync2 } from "node:fs";
58278
+ import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync16, unlinkSync as unlinkSync2 } from "node:fs";
58183
58279
  import { connect as connect2 } from "node:net";
58184
- import { join as join20, resolve as resolve7 } from "node:path";
58280
+ import { join as join21, resolve as resolve7 } from "node:path";
58185
58281
  var EDITOR_RECORD_PROTOCOL = 1;
58186
58282
  var LSP_WAIT_SECONDS = 20;
58187
58283
  var HOST = "127.0.0.1";
58188
58284
  var LOOK_AGAIN_MS = 500;
58189
58285
  function editorRecordsIn(projectPath) {
58190
- const directory = join20(projectPath, ".godot", "gdharness-editors");
58286
+ const directory = join21(projectPath, ".godot", "gdharness-editors");
58191
58287
  let names;
58192
58288
  try {
58193
58289
  names = readdirSync10(directory).filter((name) => /^\d+\.json$/.test(name));
@@ -58196,9 +58292,9 @@ function editorRecordsIn(projectPath) {
58196
58292
  }
58197
58293
  const records = [];
58198
58294
  for (const name of names) {
58199
- const file = join20(directory, name);
58295
+ const file = join21(directory, name);
58200
58296
  try {
58201
- const said = JSON.parse(readFileSync15(file, "utf8"));
58297
+ const said = JSON.parse(readFileSync16(file, "utf8"));
58202
58298
  const { protocol, pid, lsp_port: lspPort, written_at_ms: writtenAtMs } = said;
58203
58299
  if (protocol === EDITOR_RECORD_PROTOCOL && typeof pid === "number" && typeof lspPort === "number" && lspPort > 0 && typeof writtenAtMs === "number") {
58204
58300
  records.push({ file, pid, lspPort, writtenAtMs });
@@ -58291,7 +58387,7 @@ function lspOptionsOf(args, cwd) {
58291
58387
  }
58292
58388
  }
58293
58389
  const projectPath = resolve7(cwd, named ?? ".");
58294
- if (!existsSync17(join20(projectPath, "project.godot"))) {
58390
+ if (!existsSync17(join21(projectPath, "project.godot"))) {
58295
58391
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
58296
58392
  }
58297
58393
  return { projectPath, port, waitSeconds };
@@ -58398,9 +58494,9 @@ init_setup();
58398
58494
  init_tool_definitions();
58399
58495
  init_tool_reference();
58400
58496
  import { existsSync as existsSync19, mkdirSync as mkdirSync7, readdirSync as readdirSync12, rmSync as rmSync10, writeFileSync as writeFileSync11 } from "node:fs";
58401
- import { basename as basename8, dirname as dirname10, join as join22 } from "node:path";
58497
+ import { basename as basename8, dirname as dirname10, join as join23 } from "node:path";
58402
58498
  var SKILL_NAME = "gdharness";
58403
- var SHARED_SKILLS = join22(".agents", "skills");
58499
+ var SHARED_SKILLS = join23(".agents", "skills");
58404
58500
  function skillMarkdown(version) {
58405
58501
  return `---
58406
58502
  name: ${SKILL_NAME}
@@ -58641,27 +58737,27 @@ what they report while starting comes back under \`engine_messages_before_operat
58641
58737
  function skillFiles(version) {
58642
58738
  return new Map([
58643
58739
  ["SKILL.md", skillMarkdown(version)],
58644
- [join22("references", "tools.md"), renderToolsMarkdown()]
58740
+ [join23("references", "tools.md"), renderToolsMarkdown()]
58645
58741
  ]);
58646
58742
  }
58647
58743
  function skillDirectories(harnesses, projectPath, exists) {
58648
- const directories = [join22(projectPath, SHARED_SKILLS, SKILL_NAME)];
58744
+ const directories = [join23(projectPath, SHARED_SKILLS, SKILL_NAME)];
58649
58745
  for (const harness of harnesses) {
58650
58746
  if (harness.skills === undefined) {
58651
58747
  continue;
58652
58748
  }
58653
- const own = join22(projectPath, harness.skills.dir);
58654
- if ((!harness.skills.shared || exists(own)) && !directories.includes(join22(own, SKILL_NAME))) {
58655
- directories.push(join22(own, SKILL_NAME));
58749
+ const own = join23(projectPath, harness.skills.dir);
58750
+ if ((!harness.skills.shared || exists(own)) && !directories.includes(join23(own, SKILL_NAME))) {
58751
+ directories.push(join23(own, SKILL_NAME));
58656
58752
  }
58657
58753
  }
58658
58754
  return directories;
58659
58755
  }
58660
58756
  function everySkillDirectory(projectPath, harnesses) {
58661
- const directories = new Set([join22(projectPath, SHARED_SKILLS, SKILL_NAME)]);
58757
+ const directories = new Set([join23(projectPath, SHARED_SKILLS, SKILL_NAME)]);
58662
58758
  for (const harness of harnesses) {
58663
58759
  if (harness.skills !== undefined) {
58664
- directories.add(join22(projectPath, harness.skills.dir, SKILL_NAME));
58760
+ directories.add(join23(projectPath, harness.skills.dir, SKILL_NAME));
58665
58761
  }
58666
58762
  }
58667
58763
  return [...directories];
@@ -58678,7 +58774,7 @@ function removeSkill(directories, projectPath) {
58678
58774
  return removed;
58679
58775
  }
58680
58776
  function pruneEmpty(directory, projectPath) {
58681
- const shared = join22(projectPath, ".agents");
58777
+ const shared = join23(projectPath, ".agents");
58682
58778
  let path = directory;
58683
58779
  while (path !== projectPath && (basename8(path) === "skills" || path === shared)) {
58684
58780
  if (readdirSync12(path).length > 0) {
@@ -58694,7 +58790,7 @@ function writeSkill(directories, version) {
58694
58790
  const replaced = existsSync19(directory);
58695
58791
  rmSync10(directory, { recursive: true, force: true });
58696
58792
  for (const [name, contents] of files) {
58697
- const path = join22(directory, name);
58793
+ const path = join23(directory, name);
58698
58794
  mkdirSync7(dirname10(path), { recursive: true });
58699
58795
  writeFileSync11(path, contents, "utf8");
58700
58796
  }
@@ -58707,9 +58803,9 @@ init_engine_launch();
58707
58803
  init_engine_slots();
58708
58804
  init_setup();
58709
58805
  init_suite_judge();
58710
- import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync18, rmSync as rmSync11 } from "node:fs";
58806
+ import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync19, rmSync as rmSync11 } from "node:fs";
58711
58807
  import { availableParallelism as availableParallelism3 } from "node:os";
58712
- import { join as join23, resolve as resolve9 } from "node:path";
58808
+ import { join as join24, resolve as resolve9 } from "node:path";
58713
58809
  import process13 from "node:process";
58714
58810
  var SHARE = "--gdh-share=";
58715
58811
  var GATHER = "--gdh-share-gather=";
@@ -58749,9 +58845,9 @@ function spreadOptionsOf(args, cwd) {
58749
58845
  }
58750
58846
  const first = positional[0];
58751
58847
  const named = first === undefined ? null : resolve9(cwd, first);
58752
- const projectNamed = named !== null && existsSync20(join23(named, "project.godot"));
58848
+ const projectNamed = named !== null && existsSync20(join24(named, "project.godot"));
58753
58849
  const projectPath = projectNamed ? named : resolve9(cwd);
58754
- if (!existsSync20(join23(projectPath, "project.godot"))) {
58850
+ if (!existsSync20(join24(projectPath, "project.godot"))) {
58755
58851
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
58756
58852
  }
58757
58853
  const scenes = positional.slice(projectNamed ? 1 : 0);
@@ -58759,7 +58855,7 @@ function spreadOptionsOf(args, cwd) {
58759
58855
  throw new EngineUsageError(scenes.length === 0 ? "gdharness spread needs the scene of the bench to play." : `gdharness spread plays one scene, and was given ${String(scenes.length)}: ${scenes.join(", ")}.`);
58760
58856
  }
58761
58857
  const scene = resPathOf(scenes[0] ?? "", projectPath, cwd);
58762
- if (!existsSync20(join23(projectPath, scene.slice("res://".length)))) {
58858
+ if (!existsSync20(join24(projectPath, scene.slice("res://".length)))) {
58763
58859
  throw new EngineUsageError(`Nothing at ${scene} in the project at ${projectPath}.`);
58764
58860
  }
58765
58861
  const benchArguments = dashes === -1 ? [] : args.slice(dashes + 1);
@@ -58830,14 +58926,14 @@ function shareFaults(played) {
58830
58926
  return faults;
58831
58927
  }
58832
58928
  async function runSpreadCommand(options, godotPath) {
58833
- if (!existsSync20(join23(options.projectPath, SHARE_SCRIPT))) {
58929
+ if (!existsSync20(join24(options.projectPath, SHARE_SCRIPT))) {
58834
58930
  throw new EngineUsageError(`res://${SHARE_SCRIPT} is not in the project, so its bench cannot ask GdhShare for outcomes. Run gdharness setup.`);
58835
58931
  }
58836
58932
  const startedAt = Date.now();
58837
- const spreadRuns = join23(options.projectPath, ".godot", "gdharness", "spread-runs");
58933
+ const spreadRuns = join24(options.projectPath, ".godot", "gdharness", "spread-runs");
58838
58934
  sweepRunDirectories(spreadRuns);
58839
- const runDirectory = join23(spreadRuns, `${String(startedAt)}-${String(process13.pid)}`);
58840
- const shareDirectory = join23(runDirectory, "share");
58935
+ const runDirectory = join24(spreadRuns, `${String(startedAt)}-${String(process13.pid)}`);
58936
+ const shareDirectory = join24(runDirectory, "share");
58841
58937
  mkdirSync8(shareDirectory, { recursive: true });
58842
58938
  const chosen = machineSlots({ directory: options.slotsDirectory, count: options.slotCount });
58843
58939
  const slotCount = chosen.count;
@@ -58876,7 +58972,7 @@ async function runSpreadCommand(options, godotPath) {
58876
58972
  share,
58877
58973
  `${INTO}${shareDirectory}`
58878
58974
  ]);
58879
- const logs = Array.from({ length: workers }, (_, index) => join23(runDirectory, `worker-${String(index)}`, "engine.log"));
58975
+ const logs = Array.from({ length: workers }, (_, index) => join24(runDirectory, `worker-${String(index)}`, "engine.log"));
58880
58976
  let done = 0;
58881
58977
  const heartbeat = setInterval(() => {
58882
58978
  console.log(`[spread] ${progressOf(logs)}`);
@@ -58894,18 +58990,18 @@ async function runSpreadCommand(options, godotPath) {
58894
58990
  }
58895
58991
  const faults = left.flatMap((worker) => engineFaults(worker, options.godotVersion));
58896
58992
  const arms = left.map((_, index) => {
58897
- const file = join23(shareDirectory, `worker-${String(index)}.arms`);
58898
- return existsSync20(file) ? armsPlayed(readFileSync18(file, "utf8")) : [];
58993
+ const file = join24(shareDirectory, `worker-${String(index)}.arms`);
58994
+ return existsSync20(file) ? armsPlayed(readFileSync19(file, "utf8")) : [];
58899
58995
  });
58900
58996
  faults.push(...shareFaults(arms));
58901
58997
  if (faults.length > 0) {
58902
58998
  return failed(faults, left, runDirectory, "so nothing was gathered");
58903
58999
  }
58904
59000
  const gather = await engine("gather", `${GATHER}${String(workers)}`);
58905
- console.log(readFileSync18(gather.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd());
59001
+ console.log(readFileSync19(gather.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd());
58906
59002
  const gatherFaults = engineFaults(gather, options.godotVersion);
58907
- const gatheredFile = join23(shareDirectory, "gathered.arms");
58908
- const gathered = new Set((existsSync20(gatheredFile) ? armsPlayed(readFileSync18(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
59003
+ const gatheredFile = join24(shareDirectory, "gathered.arms");
59004
+ const gathered = new Set((existsSync20(gatheredFile) ? armsPlayed(readFileSync19(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
58909
59005
  const ungathered = (arms[0] ?? []).filter((arm) => !gathered.has(arm.arm)).map((arm) => arm.arm);
58910
59006
  if (ungathered.length > 0) {
58911
59007
  gatherFaults.push(`the gathering run never asked for ${ungathered.join(", ")}, which the workers played: it went another way through the arms than they did`);
@@ -58925,9 +59021,9 @@ async function runSpreadCommand(options, godotPath) {
58925
59021
  }
58926
59022
  }
58927
59023
  async function played(launch, slots, options, runDirectory, name, benchArguments) {
58928
- const directory = join23(runDirectory, name);
58929
- mkdirSync8(join23(directory, "user"), { recursive: true });
58930
- const log = join23(directory, "engine.log");
59024
+ const directory = join24(runDirectory, name);
59025
+ mkdirSync8(join24(directory, "user"), { recursive: true });
59026
+ const log = join24(directory, "engine.log");
58931
59027
  const args = [
58932
59028
  "--headless",
58933
59029
  "--audio-driver",
@@ -58959,7 +59055,7 @@ function engineFaults(engine, godotVersion) {
58959
59055
  if (!existsSync20(engine.log)) {
58960
59056
  return faults;
58961
59057
  }
58962
- const printed = readRun(readFileSync18(engine.log, "utf8"));
59058
+ const printed = readRun(readFileSync19(engine.log, "utf8"));
58963
59059
  const wrongEngine = godotVersion === null ? null : versionFault(printed.engineVersion, godotVersion);
58964
59060
  if (wrongEngine !== null) {
58965
59061
  faults.push(`${engine.name}: ${wrongEngine}`);
@@ -58972,7 +59068,7 @@ function engineFaults(engine, godotVersion) {
58972
59068
  }
58973
59069
  function progressOf(logs) {
58974
59070
  const each = logs.map((log, index) => {
58975
- const text = existsSync20(log) ? readFileSync18(log, "utf8") : "";
59071
+ const text = existsSync20(log) ? readFileSync19(log, "utf8") : "";
58976
59072
  const last = [...text.matchAll(PROGRESS)].at(-1);
58977
59073
  return last === undefined ? `worker ${String(index)} has played no arm yet` : `worker ${String(index)} at ${last[1] ?? ""} ${last[2] ?? ""} of ${last[3] ?? ""}`;
58978
59074
  });
@@ -59004,20 +59100,20 @@ import {
59004
59100
  existsSync as existsSync23,
59005
59101
  mkdirSync as mkdirSync10,
59006
59102
  readdirSync as readdirSync14,
59007
- readFileSync as readFileSync22,
59103
+ readFileSync as readFileSync23,
59008
59104
  rmSync as rmSync12,
59009
59105
  statSync as statSync14,
59010
59106
  writeFileSync as writeFileSync12
59011
59107
  } from "node:fs";
59012
59108
  import { availableParallelism as availableParallelism4 } from "node:os";
59013
- import { dirname as dirname13, join as join27, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
59109
+ import { dirname as dirname13, join as join28, relative as relative5, resolve as resolve10, sep as sep4 } from "node:path";
59014
59110
  import process14 from "node:process";
59015
59111
 
59016
59112
  // src/suite-fingerprint.ts
59017
59113
  init_class_cache();
59018
59114
  import { createHash as createHash6 } from "node:crypto";
59019
- import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync19, statSync as statSync13 } from "node:fs";
59020
- import { join as join24, posix as posix2 } from "node:path";
59115
+ import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync20, statSync as statSync13 } from "node:fs";
59116
+ import { join as join25, posix as posix2 } from "node:path";
59021
59117
  var RES_LITERAL = /res:\/\/[^"'\s)\]<>]*/g;
59022
59118
  var RELATIVE_LOAD = /\b(?:pre)?load\(\s*["']((?!res:\/\/|uid:\/\/)[^"']+)["']/g;
59023
59119
  var REACHES_AT_RUN_TIME = [
@@ -59076,16 +59172,16 @@ ${this.read(path).digest}
59076
59172
  const hash = createHash6("sha256");
59077
59173
  const walk = (directory, under) => {
59078
59174
  for (const entry of readdirSync13(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
59079
- const file = join24(directory, entry.name);
59175
+ const file = join25(directory, entry.name);
59080
59176
  if (entry.isDirectory()) {
59081
59177
  walk(file, `${under}${entry.name}/`);
59082
59178
  } else if (entry.isFile()) {
59083
59179
  hash.update(`${under}${entry.name}
59084
- `).update(readFileSync19(file));
59180
+ `).update(readFileSync20(file));
59085
59181
  }
59086
59182
  }
59087
59183
  };
59088
- const directory = join24(this.scripts.projectPath, root.slice("res://".length));
59184
+ const directory = join25(this.scripts.projectPath, root.slice("res://".length));
59089
59185
  if (existsSync21(directory)) {
59090
59186
  walk(directory, "");
59091
59187
  }
@@ -59095,7 +59191,7 @@ ${this.read(path).digest}
59095
59191
  if (PLACEHOLDER.test(path)) {
59096
59192
  return { digest: "", reaches: [], unreadable: `${path} is filled in at run time` };
59097
59193
  }
59098
- const file = join24(this.scripts.projectPath, path.slice("res://".length));
59194
+ const file = join25(this.scripts.projectPath, path.slice("res://".length));
59099
59195
  if (!existsSync21(file)) {
59100
59196
  return { digest: "missing", reaches: [], unreadable: null };
59101
59197
  }
@@ -59107,7 +59203,7 @@ ${this.read(path).digest}
59107
59203
  if (BINARY_RESOURCE.test(path)) {
59108
59204
  return { digest: "", reaches: [], unreadable: `${path} is a binary resource` };
59109
59205
  }
59110
- const bytes = readFileSync19(file);
59206
+ const bytes = readFileSync20(file);
59111
59207
  const digest = createHash6("sha256").update(bytes).digest("hex");
59112
59208
  const text = bytes.toString("latin1");
59113
59209
  const reaches = new Set;
@@ -59144,8 +59240,8 @@ ${this.read(path).digest}
59144
59240
  }
59145
59241
  }
59146
59242
  function extensionsOf(projectPath) {
59147
- const list = join24(projectPath, ".godot", "extension_list.cfg");
59148
- return existsSync21(list) ? readFileSync19(list, "utf8").split(`
59243
+ const list = join25(projectPath, ".godot", "extension_list.cfg");
59244
+ return existsSync21(list) ? readFileSync20(list, "utf8").split(`
59149
59245
  `).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
59150
59246
  }
59151
59247
 
@@ -59154,18 +59250,18 @@ init_suite_judge();
59154
59250
  init_suite_plan();
59155
59251
 
59156
59252
  // src/version.ts
59157
- import { readFileSync as readFileSync21 } from "node:fs";
59158
- import { dirname as dirname12, join as join26 } from "node:path";
59253
+ import { readFileSync as readFileSync22 } from "node:fs";
59254
+ import { dirname as dirname12, join as join27 } from "node:path";
59159
59255
  import { fileURLToPath as fileURLToPath3 } from "node:url";
59160
59256
  function getLocalVersion() {
59161
59257
  const currentDirectory = dirname12(fileURLToPath3(import.meta.url));
59162
59258
  const packageCandidates = [
59163
- join26(currentDirectory, "..", "package.json"),
59164
- join26(currentDirectory, "..", "..", "package.json")
59259
+ join27(currentDirectory, "..", "package.json"),
59260
+ join27(currentDirectory, "..", "..", "package.json")
59165
59261
  ];
59166
59262
  for (const packagePath of packageCandidates) {
59167
59263
  try {
59168
- const value = JSON.parse(readFileSync21(packagePath, "utf-8"));
59264
+ const value = JSON.parse(readFileSync22(packagePath, "utf-8"));
59169
59265
  if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
59170
59266
  return value.version;
59171
59267
  }
@@ -59232,9 +59328,9 @@ function testOptionsOf(args, cwd) {
59232
59328
  }
59233
59329
  const first = positional[0];
59234
59330
  const named = first === undefined ? null : resolve10(cwd, first);
59235
- const projectNamed = named !== null && existsSync23(join27(named, "project.godot"));
59331
+ const projectNamed = named !== null && existsSync23(join28(named, "project.godot"));
59236
59332
  const projectPath = projectNamed ? named : resolve10(cwd);
59237
- if (!existsSync23(join27(projectPath, "project.godot"))) {
59333
+ if (!existsSync23(join28(projectPath, "project.godot"))) {
59238
59334
  throw new EngineUsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
59239
59335
  }
59240
59336
  const last = (flag) => valued.get(flag)?.at(-1);
@@ -59268,11 +59364,11 @@ function seedOf(value) {
59268
59364
  return seed;
59269
59365
  }
59270
59366
  function rootsOf(options) {
59271
- return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync23(join27(options.projectPath, root))).map((root) => `res://${root}`);
59367
+ return options.roots.length > 0 ? options.roots : TEST_DIRECTORIES.filter((root) => existsSync23(join28(options.projectPath, root))).map((root) => `res://${root}`);
59272
59368
  }
59273
59369
  function searchedRoots(options) {
59274
59370
  return options.selected.length === 0 ? rootsOf(options) : options.selected.filter((entry) => {
59275
- const directory = join27(options.projectPath, entry.slice("res://".length));
59371
+ const directory = join28(options.projectPath, entry.slice("res://".length));
59276
59372
  return existsSync23(directory) && statSync14(directory).isDirectory();
59277
59373
  });
59278
59374
  }
@@ -59289,7 +59385,7 @@ function selectedSuites(scripts, options) {
59289
59385
  for (const entry of options.selected) {
59290
59386
  const caseAt = /^(res:\/\/.*\.gd):(\w+)$/.exec(entry);
59291
59387
  const path = caseAt?.[1] ?? entry;
59292
- const directory = join27(options.projectPath, path.slice(6));
59388
+ const directory = join28(options.projectPath, path.slice(6));
59293
59389
  const suites = existsSync23(directory) && statSync14(directory).isDirectory() ? scripts.suitesUnder([path.replace(/\/$/, "")]) : [scripts.suite(path)].filter((suite) => suite !== null);
59294
59390
  if (suites.length === 0) {
59295
59391
  throw new EngineUsageError(existsSync23(directory) ? `${path} holds no gdUnit4 suite: nothing there extends GdUnitTestSuite and declares a test_ function.` : `Nothing at ${path} in the project at ${options.projectPath}.`);
@@ -59346,7 +59442,7 @@ class RunLogs {
59346
59442
  return this.kept ?? this.directory;
59347
59443
  }
59348
59444
  named(path) {
59349
- return this.kept === null ? path : join27(this.kept, relative5(this.directory, path));
59445
+ return this.kept === null ? path : join28(this.kept, relative5(this.directory, path));
59350
59446
  }
59351
59447
  keep() {
59352
59448
  if (this.kept === null) {
@@ -59354,8 +59450,8 @@ class RunLogs {
59354
59450
  }
59355
59451
  for (const file of readdirSync14(this.directory, { recursive: true, encoding: "utf8" })) {
59356
59452
  if (file.endsWith(".log") && file.split(sep4).length === 2) {
59357
- mkdirSync10(dirname13(join27(this.kept, file)), { recursive: true });
59358
- copyFileSync(join27(this.directory, file), join27(this.kept, file));
59453
+ mkdirSync10(dirname13(join28(this.kept, file)), { recursive: true });
59454
+ copyFileSync(join28(this.directory, file), join28(this.kept, file));
59359
59455
  }
59360
59456
  }
59361
59457
  }
@@ -59368,24 +59464,24 @@ async function runTestCommand(options, godotPath) {
59368
59464
  const startedAt = Date.now();
59369
59465
  const scripts = new ProjectScripts(options.projectPath);
59370
59466
  const home = homeOf(options.projectPath);
59371
- const state = join27(home, ".godot", "gdharness");
59372
- const timesFile = join27(state, "suite-times.json");
59373
- const passedFile = join27(state, "suite-passed.json");
59374
- const lastRunFile = join27(state, LAST_RUN_FILE);
59467
+ const state = join28(home, ".godot", "gdharness");
59468
+ const timesFile = join28(state, "suite-times.json");
59469
+ const passedFile = join28(state, "suite-passed.json");
59470
+ const lastRunFile = join28(state, LAST_RUN_FILE);
59375
59471
  let suites = selectedSuites(scripts, options);
59376
59472
  const uncollected = uncollectedSaid(scripts.uncollectedUnder(searchedRoots(options)));
59377
59473
  const wholeSuites = suites.map((suite) => scripts.suite(suite.path) ?? suite);
59378
59474
  const declared = new Map(wholeSuites.map((suite) => [suite.path, suite.cases]));
59379
- const testRuns = join27(options.projectPath, ".godot", "gdharness", "test-runs");
59380
- const keptRuns = join27(state, "test-runs");
59475
+ const testRuns = join28(options.projectPath, ".godot", "gdharness", "test-runs");
59476
+ const keptRuns = join28(state, "test-runs");
59381
59477
  sweepRunDirectories(testRuns);
59382
59478
  if (keptRuns !== testRuns) {
59383
59479
  sweepRunDirectories(keptRuns);
59384
59480
  }
59385
59481
  const runName = `${String(startedAt)}-${String(process14.pid)}`;
59386
- const runDirectory = join27(testRuns, runName);
59482
+ const runDirectory = join28(testRuns, runName);
59387
59483
  mkdirSync10(runDirectory, { recursive: true });
59388
- const logs = new RunLogs(runDirectory, keptRuns === testRuns ? null : join27(keptRuns, runName));
59484
+ const logs = new RunLogs(runDirectory, keptRuns === testRuns ? null : join28(keptRuns, runName));
59389
59485
  const chosen = machineSlots({ directory: options.slotsDirectory, count: options.slotCount });
59390
59486
  const slots = new EngineSlots(chosen.directory, chosen.count, { pid: process14.pid, started: await ownStart(), by: `gdharness test of ${options.projectPath}` }, (line) => {
59391
59487
  console.log(line);
@@ -59426,9 +59522,9 @@ async function runTestCommand(options, godotPath) {
59426
59522
  }
59427
59523
  const fingerprints = new SuiteFingerprints(scripts, [
59428
59524
  getLocalVersion(),
59429
- readFileSync22(shippedListedRunner(), "utf8"),
59525
+ readFileSync23(shippedListedRunner(), "utf8"),
59430
59526
  ...options.reachAlso.map((path) => `${path}
59431
- ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
59527
+ ${existsSync23(path) ? readFileSync23(path, "latin1") : "missing"}`)
59432
59528
  ]);
59433
59529
  const printsOf = new Map(suites.map((suite) => [suite.path, fingerprints.of(suite)]));
59434
59530
  if (options.changedOnly) {
@@ -59544,7 +59640,7 @@ function roomSaid(engines, lanes, unheld, held, asked) {
59544
59640
  }
59545
59641
  function readRecord(file) {
59546
59642
  try {
59547
- const parsed = JSON.parse(readFileSync22(file, "utf8"));
59643
+ const parsed = JSON.parse(readFileSync23(file, "utf8"));
59548
59644
  return typeof parsed === "object" && parsed !== null ? parsed : {};
59549
59645
  } catch {
59550
59646
  return {};
@@ -59593,9 +59689,9 @@ async function importProject(launch) {
59593
59689
  console.log("The import was skipped: every imported file and the class cache are current.");
59594
59690
  return null;
59595
59691
  }
59596
- const directory = join27(launch.runDirectory, "import");
59597
- mkdirSync10(join27(directory, "user"), { recursive: true });
59598
- const log = join27(directory, "import.log");
59692
+ const directory = join28(launch.runDirectory, "import");
59693
+ mkdirSync10(join28(directory, "user"), { recursive: true });
59694
+ const log = join28(directory, "import.log");
59599
59695
  const lock = await launch.slots.take();
59600
59696
  try {
59601
59697
  freeLibraryCopies(launch.projectPath);
@@ -59603,7 +59699,7 @@ async function importProject(launch) {
59603
59699
  if (ended.ended !== null || ended.code !== 0) {
59604
59700
  return `${ended.ended ?? `the engine exited ${exitNamed(ended.code ?? 0)}`}; everything it printed is in ${launch.logs.named(log)}`;
59605
59701
  }
59606
- const printed = readRun(readFileSync22(log, "utf8"));
59702
+ const printed = readRun(readFileSync23(log, "utf8"));
59607
59703
  const wrongEngine = launch.godotVersion === null ? null : versionFault(printed.engineVersion, launch.godotVersion);
59608
59704
  if (wrongEngine !== null) {
59609
59705
  return `${wrongEngine}; everything it printed is in ${launch.logs.named(log)}`;
@@ -59660,12 +59756,12 @@ async function runShares(launch, units, declared, lanes, taken, times) {
59660
59756
  return (await Promise.all(Array.from({ length: lanes }, (_, at) => lane(at)))).flat().sort((a, b) => a.index - b.index);
59661
59757
  }
59662
59758
  async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59663
- const directory = join27(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
59664
- mkdirSync10(join27(directory, "user"), { recursive: true });
59665
- const suitesFile = join27(directory, "suites.txt");
59666
- const skippingFile = join27(directory, "skipping.txt");
59667
- const rowsFile = join27(directory, "rows.txt");
59668
- const expectedFile = join27(directory, "expected.json");
59759
+ const directory = join28(launch.runDirectory, `engine-${String(index)}${again === "" ? "" : `-${again}`}`);
59760
+ mkdirSync10(join28(directory, "user"), { recursive: true });
59761
+ const suitesFile = join28(directory, "suites.txt");
59762
+ const skippingFile = join28(directory, "skipping.txt");
59763
+ const rowsFile = join28(directory, "rows.txt");
59764
+ const expectedFile = join28(directory, "expected.json");
59669
59765
  writeFileSync12(suitesFile, `${plan.suites.map((suite) => suite.path).join(`
59670
59766
  `)}
59671
59767
  `);
@@ -59692,7 +59788,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59692
59788
  ...plan.windowed ? [] : ["--ignoreHeadlessMode"],
59693
59789
  "-c",
59694
59790
  "-rd",
59695
- `res://${relative5(launch.projectPath, join27(directory, "reports")).split(sep4).join("/")}`,
59791
+ `res://${relative5(launch.projectPath, join28(directory, "reports")).split(sep4).join("/")}`,
59696
59792
  "-rc",
59697
59793
  "1",
59698
59794
  "--",
@@ -59708,7 +59804,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59708
59804
  "--expected-out",
59709
59805
  expectedFile
59710
59806
  ];
59711
- const log = join27(directory, "engine.log");
59807
+ const log = join28(directory, "engine.log");
59712
59808
  const lock = slot ?? await launch.slots.take();
59713
59809
  const began = Date.now();
59714
59810
  let end;
@@ -59719,7 +59815,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59719
59815
  launch.slots.release(lock);
59720
59816
  }
59721
59817
  }
59722
- const printed = readRun(readFileSync22(log, "utf8"));
59818
+ const printed = readRun(readFileSync23(log, "utf8"));
59723
59819
  const assignment = {
59724
59820
  suites: plan.suites,
59725
59821
  expected: readExpected(expectedFile),
@@ -59727,7 +59823,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59727
59823
  godotVersion: launch.godotVersion
59728
59824
  };
59729
59825
  const judged = judgeEngine(printed, assignment, end);
59730
- const written = reportsFault(join27(directory, "reports"));
59826
+ const written = reportsFault(join28(directory, "reports"));
59731
59827
  return {
59732
59828
  index,
59733
59829
  plan,
@@ -59741,7 +59837,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59741
59837
  }
59742
59838
  var REPORT_FILES_NAMED = 3;
59743
59839
  function reportsFault(reports) {
59744
- const files = existsSync23(reports) ? readdirSync14(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join27(reports, file)).isFile()).sort() : [];
59840
+ const files = existsSync23(reports) ? readdirSync14(reports, { recursive: true, encoding: "utf8" }).filter((file) => statSync14(join28(reports, file)).isFile()).sort() : [];
59745
59841
  if (files.length === 0) {
59746
59842
  return null;
59747
59843
  }
@@ -59754,7 +59850,7 @@ function report(ran, across, uncollected, startedAt, logs, printOutput, targets)
59754
59850
  for (const engine of ran) {
59755
59851
  console.log("");
59756
59852
  console.log(`--- what engine ${String(engine.index)} printed, running ${named(engine.plan.suites.map((suite) => suite.path))}:`);
59757
- console.log(existsSync23(engine.log) ? readFileSync22(engine.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd() : "(no log was written)");
59853
+ console.log(existsSync23(engine.log) ? readFileSync23(engine.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd() : "(no log was written)");
59758
59854
  }
59759
59855
  }
59760
59856
  const failed = ran.filter((engine) => !engine.verdict.passed);
@@ -59773,7 +59869,7 @@ function report(ran, across, uncollected, startedAt, logs, printOutput, targets)
59773
59869
  writeFileSync12(engine.log, `${verdict.join(`
59774
59870
  `)}
59775
59871
  ---
59776
- ${readFileSync22(engine.log, "utf8")}`);
59872
+ ${readFileSync23(engine.log, "utf8")}`);
59777
59873
  }
59778
59874
  if (engine.verdict.shown.length > 0) {
59779
59875
  console.log("");
@@ -59899,7 +59995,7 @@ class UsageError extends Refusal {
59899
59995
  function projectArgument(at) {
59900
59996
  const named = args.slice(at).find((value) => !value.startsWith("--"));
59901
59997
  const projectPath = resolve14(named ?? ".");
59902
- if (!existsSync35(join44(projectPath, "project.godot"))) {
59998
+ if (!existsSync35(join45(projectPath, "project.godot"))) {
59903
59999
  throw new UsageError(`Not a Godot project: ${projectPath} holds no project.godot.`);
59904
60000
  }
59905
60001
  return projectPath;
@@ -59963,7 +60059,7 @@ async function registerRuntime(godot, projectPath) {
59963
60059
  }
59964
60060
  const path = (named ?? "").replace(/^res:\/\//, "");
59965
60061
  console.log(`${RUNTIME_AUTOLOAD.name} autoload left at ${named}, which this project registers itself`);
59966
- if (!existsSync35(join44(projectPath, path))) {
60062
+ if (!existsSync35(join45(projectPath, path))) {
59967
60063
  console.log(" that file is not in this project, so it boots with a missing script");
59968
60064
  }
59969
60065
  console.log(` ${useTheShippedLoader(RUNTIME_AUTOLOAD.name, path)}`);