gdharness 1.3.43 → 1.3.44

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);
@@ -11761,9 +11825,9 @@ function stillGoingSaid(tool, op, elapsedMs) {
11761
11825
  const took = seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
11762
11826
  return `${op === null || op === "" ? tool : `${tool} ${op}`} is still going; ${took} in.`;
11763
11827
  }
11764
- async function withCallSignal(signal, answer, progress, still) {
11828
+ async function withCallSignal(signal, answer, progress, still, slotWaits) {
11765
11829
  if (progress === undefined) {
11766
- return signal === undefined ? answer() : current.run({ signal, progress: undefined }, answer);
11830
+ return signal === undefined && slotWaits === undefined ? answer() : current.run({ signal, progress: undefined, slotWaits }, answer);
11767
11831
  }
11768
11832
  const began = Date.now();
11769
11833
  const rising = risingOnly(progress);
@@ -11787,7 +11851,7 @@ async function withCallSignal(signal, answer, progress, still) {
11787
11851
  };
11788
11852
  rearm();
11789
11853
  try {
11790
- return await current.run({ signal, progress: told }, answer);
11854
+ return await current.run({ signal, progress: told, slotWaits }, answer);
11791
11855
  } finally {
11792
11856
  going = undefined;
11793
11857
  clearTimeout(quiet);
@@ -11806,63 +11870,20 @@ function callSignal() {
11806
11870
  function callProgress() {
11807
11871
  return current.getStore()?.progress;
11808
11872
  }
11873
+ function callSlotWaits() {
11874
+ return current.getStore()?.slotWaits;
11875
+ }
11809
11876
  var current, CALL_HEARTBEAT_MS = 20000;
11810
11877
  var init_call_signal = __esm(() => {
11811
11878
  current = new AsyncLocalStorage;
11812
11879
  });
11813
11880
 
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
11881
  // src/engine-slots.ts
11861
11882
  import {
11862
11883
  closeSync as closeSync3,
11863
11884
  mkdirSync as mkdirSync3,
11864
11885
  openSync as openSync3,
11865
- readFileSync as readFileSync7,
11886
+ readFileSync as readFileSync8,
11866
11887
  rmSync as rmSync6,
11867
11888
  statSync as statSync6,
11868
11889
  watch,
@@ -11992,7 +12013,7 @@ class EngineSlots {
11992
12013
  this.holder = holder;
11993
12014
  this.say = say;
11994
12015
  }
11995
- async take(signal, waiting) {
12016
+ async take(signal, waiting, blocked) {
11996
12017
  mkdirSync3(this.directory, { recursive: true });
11997
12018
  const began = Date.now();
11998
12019
  let said = Number.NEGATIVE_INFINITY;
@@ -12005,7 +12026,10 @@ class EngineSlots {
12005
12026
  if (lock !== null) {
12006
12027
  return lock;
12007
12028
  }
12008
- mark ??= this.waitMarked();
12029
+ if (mark === undefined) {
12030
+ mark = this.waitMarked();
12031
+ blocked?.();
12032
+ }
12009
12033
  const now = Date.now();
12010
12034
  if (waiting !== undefined) {
12011
12035
  if (now - said >= WAIT_SAID_MS) {
@@ -12059,7 +12083,7 @@ class EngineSlots {
12059
12083
  const found = [];
12060
12084
  for (let slot = 0;slot < this.count; slot += 1) {
12061
12085
  try {
12062
- const holder = holderOf(readFileSync7(join8(this.directory, `${String(slot)}.lock`), "utf8"));
12086
+ const holder = holderOf(readFileSync8(join8(this.directory, `${String(slot)}.lock`), "utf8"));
12063
12087
  if (holder !== null) {
12064
12088
  found.push(holder);
12065
12089
  }
@@ -12079,7 +12103,7 @@ class EngineSlots {
12079
12103
  release(lock) {
12080
12104
  this.held.delete(lock);
12081
12105
  try {
12082
- if (holderOf(readFileSync7(lock, "utf8"))?.pid === this.holder.pid) {
12106
+ if (holderOf(readFileSync8(lock, "utf8"))?.pid === this.holder.pid) {
12083
12107
  removeWhole(lock);
12084
12108
  }
12085
12109
  } catch {}
@@ -12129,7 +12153,7 @@ class EngineSlots {
12129
12153
  let content;
12130
12154
  let writtenAt;
12131
12155
  try {
12132
- content = readFileSync7(lock, "utf8");
12156
+ content = readFileSync8(lock, "utf8");
12133
12157
  writtenAt = statSync6(lock).mtimeMs;
12134
12158
  } catch {
12135
12159
  return false;
@@ -12167,31 +12191,55 @@ function nameSlotTaker(by) {
12167
12191
  }
12168
12192
  function slotWaitSaid(waitedMs, count, directory, holders = []) {
12169
12193
  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`;
12194
+ const named = holdersNamed(count, holders);
12195
+ const waiting = `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine: ${slots}`;
12196
+ if (named.length === 0) {
12197
+ return `${waiting}, by a gate or another tool, and the engine starts when one is given back.`;
12198
+ }
12199
+ if (count === 1) {
12200
+ return `${waiting}, by ${named.join(", ")}, and the engine starts when it is given back.`;
12201
+ }
12202
+ return `${waiting}: ${listed(named)}. The engine starts when one is given back.`;
12203
+ }
12204
+ function slotWaitedSaid(waitedMs, count, directory, holders) {
12205
+ const waited = `This call waited ${String(Math.round(waitedMs / 1000))} s for an engine slot in all.`;
12206
+ 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`;
12207
+ const named = holdersNamed(count, holders);
12208
+ if (named.length === 0) {
12209
+ return `${waited} When it first waited, ${slots}, by a gate or another tool.`;
12210
+ }
12211
+ return count === 1 ? `${waited} When it first waited, ${slots}, by ${named.join(", ")}.` : `${waited} When it first waited, ${slots}: ${listed(named)}.`;
12212
+ }
12213
+ function holdersNamed(count, holders) {
12170
12214
  const held = new Map;
12171
12215
  for (const holder of holders) {
12172
12216
  const known = held.get(holder.pid);
12173
12217
  held.set(holder.pid, { by: known?.by ?? holder.by, slots: (known?.slots ?? 0) + 1 });
12174
12218
  }
12175
- const named = [...held].map(([pid, { by, slots: taken }]) => {
12219
+ return [...held].map(([pid, { by, slots: taken }]) => {
12176
12220
  const who = pid === process7.pid ? `this process (pid ${String(pid)})` : by === undefined ? `pid ${String(pid)}` : `${by} (pid ${String(pid)})`;
12177
12221
  return count === 1 ? who : `${String(taken)} by ${who}`;
12178
12222
  });
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.`;
12223
+ }
12224
+ function listed(named) {
12225
+ return named.length === 1 ? named.join("") : `${named.slice(0, -1).join(", ")} and ${named.at(-1) ?? ""}`;
12188
12226
  }
12189
12227
  async function slotForCall() {
12190
12228
  const slots = await processSlots();
12191
12229
  const progress = callProgress();
12230
+ const waits = callSlotWaits();
12231
+ const began = Date.now();
12232
+ let blockedBy;
12192
12233
  const lock = await slots.take(callSignal(), progress === undefined ? undefined : (waitedMs) => {
12193
12234
  progress(waitedMs / 1000, slotWaitSaid(waitedMs, slots.count, slots.directory, slots.holders()));
12235
+ }, () => {
12236
+ blockedBy = slots.holders();
12194
12237
  });
12238
+ if (waits !== undefined && blockedBy !== undefined) {
12239
+ waits.ms += Date.now() - began;
12240
+ const heldBy = blockedBy;
12241
+ waits.said ??= (waitedMs) => slotWaitedSaid(waitedMs, slots.count, slots.directory, heldBy);
12242
+ }
12195
12243
  return {
12196
12244
  release: () => {
12197
12245
  slots.release(lock);
@@ -12961,7 +13009,7 @@ var init_doc_cache = __esm(() => {
12961
13009
  });
12962
13010
 
12963
13011
  // src/project-scan.ts
12964
- import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync8, statSync as statSync9 } from "node:fs";
13012
+ import { existsSync as existsSync9, readdirSync as readdirSync6, readFileSync as readFileSync9, statSync as statSync9 } from "node:fs";
12965
13013
  import { basename as basename5, extname, join as join11, relative as relative4, sep as sep3 } from "node:path";
12966
13014
  function skipped(name) {
12967
13015
  return name.startsWith(".") || SKIPPED.has(name);
@@ -13097,7 +13145,7 @@ function scriptsWithoutUid(projectPath) {
13097
13145
  function pathsInUidCache(projectPath) {
13098
13146
  let data;
13099
13147
  try {
13100
- data = readFileSync8(join11(projectPath, ".godot", "uid_cache.bin"));
13148
+ data = readFileSync9(join11(projectPath, ".godot", "uid_cache.bin"));
13101
13149
  } catch {
13102
13150
  return null;
13103
13151
  }
@@ -13159,7 +13207,7 @@ function projectTexts(projectPath) {
13159
13207
  if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
13160
13208
  continue;
13161
13209
  }
13162
- const bytes = readFileSync8(absolute);
13210
+ const bytes = readFileSync9(absolute);
13163
13211
  if (!isBinary(bytes)) {
13164
13212
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
13165
13213
  }
@@ -13189,7 +13237,7 @@ function ignoredTexts(projectPath) {
13189
13237
  if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
13190
13238
  continue;
13191
13239
  }
13192
- const bytes = readFileSync8(absolute);
13240
+ const bytes = readFileSync9(absolute);
13193
13241
  if (!isBinary(bytes)) {
13194
13242
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
13195
13243
  }
@@ -13226,7 +13274,7 @@ function searchProject(projectPath, options) {
13226
13274
  };
13227
13275
  const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
13228
13276
  const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
13229
- const full = () => result.summary.truncated;
13277
+ let shown = 0;
13230
13278
  const notSearched = [];
13231
13279
  const met = new Set;
13232
13280
  const searchFile = (entryPath, name) => {
@@ -13237,28 +13285,33 @@ function searchProject(projectPath, options) {
13237
13285
  if (extensions !== null && !extensions.has(extension)) {
13238
13286
  return;
13239
13287
  }
13240
- const bytes = readFileSync8(entryPath);
13288
+ const bytes = readFileSync9(entryPath);
13241
13289
  if (extensions === null && isBinary(bytes)) {
13242
13290
  return;
13243
13291
  }
13244
13292
  result.summary.files_searched += 1;
13245
13293
  const matches = [];
13294
+ let matched = false;
13246
13295
  for (const [index, line] of bytes.toString("utf8").split(/\r?\n/).entries()) {
13247
13296
  const found = regex ? regex.exec(line) : plainMatch(line, needle, options.caseSensitive);
13248
13297
  if (found === null) {
13249
13298
  continue;
13250
13299
  }
13251
- if (result.summary.total_matches >= options.maxResults) {
13300
+ matched = true;
13301
+ result.summary.total_matches += 1;
13302
+ if (shown >= options.maxResults) {
13252
13303
  result.summary.truncated = true;
13253
- break;
13304
+ continue;
13254
13305
  }
13306
+ shown += 1;
13255
13307
  matches.push({ line: index + 1, content: line.trim(), match: found[0] });
13256
- result.summary.total_matches += 1;
13308
+ }
13309
+ if (matched) {
13310
+ result.summary.files_with_matches += 1;
13257
13311
  }
13258
13312
  if (matches.length > 0) {
13259
13313
  const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
13260
13314
  result.results.push({ file: `res://${relativePath}`, matches });
13261
- result.summary.files_with_matches += 1;
13262
13315
  }
13263
13316
  };
13264
13317
  const visit = (directory, named = false) => {
@@ -13269,9 +13322,6 @@ function searchProject(projectPath, options) {
13269
13322
  return;
13270
13323
  }
13271
13324
  for (const entry of readdirSync6(directory, { withFileTypes: true })) {
13272
- if (full()) {
13273
- return;
13274
- }
13275
13325
  if (skipped(entry.name)) {
13276
13326
  continue;
13277
13327
  }
@@ -13351,7 +13401,7 @@ var init_project_scan = __esm(() => {
13351
13401
 
13352
13402
  // src/import-freshness.ts
13353
13403
  import { createHash as createHash2 } from "node:crypto";
13354
- import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync9, statSync as statSync10 } from "node:fs";
13404
+ import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync10 } from "node:fs";
13355
13405
  import { join as join12 } from "node:path";
13356
13406
  function md5Of(file) {
13357
13407
  let stats;
@@ -13366,7 +13416,7 @@ function md5Of(file) {
13366
13416
  }
13367
13417
  let md5;
13368
13418
  try {
13369
- md5 = createHash2("md5").update(readFileSync9(file)).digest("hex");
13419
+ md5 = createHash2("md5").update(readFileSync10(file)).digest("hex");
13370
13420
  } catch {
13371
13421
  return null;
13372
13422
  }
@@ -13392,7 +13442,7 @@ function sectionOf(text, section) {
13392
13442
  function unquoted(value) {
13393
13443
  return value === undefined ? "" : value.replace(/^"(.*)"$/, "$1");
13394
13444
  }
13395
- function listed(value) {
13445
+ function listed2(value) {
13396
13446
  return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
13397
13447
  }
13398
13448
  function importedIntoTheProject(projectPath, resPaths) {
@@ -13400,11 +13450,11 @@ function importedIntoTheProject(projectPath, resPaths) {
13400
13450
  for (const resPath of resPaths) {
13401
13451
  let sidecar;
13402
13452
  try {
13403
- sidecar = readFileSync9(`${onDisk(projectPath, resPath)}.import`, "utf8");
13453
+ sidecar = readFileSync10(`${onDisk(projectPath, resPath)}.import`, "utf8");
13404
13454
  } catch {
13405
13455
  continue;
13406
13456
  }
13407
- const outputs = listed(sectionOf(sidecar, "deps").get("dest_files")).filter((output) => output.startsWith("res://") && !output.startsWith("res://.godot/"));
13457
+ const outputs = listed2(sectionOf(sidecar, "deps").get("dest_files")).filter((output) => output.startsWith("res://") && !output.startsWith("res://.godot/"));
13408
13458
  if (outputs.length > 0) {
13409
13459
  wrote[resPath] = outputs;
13410
13460
  }
@@ -13420,7 +13470,7 @@ function behind(projectPath, resPath, sidecar) {
13420
13470
  if (importer === "keep" || importer === "skip") {
13421
13471
  return null;
13422
13472
  }
13423
- const outputs = listed(sectionOf(sidecar, "deps").get("dest_files"));
13473
+ const outputs = listed2(sectionOf(sidecar, "deps").get("dest_files"));
13424
13474
  if (outputs.some((output) => !existsSync10(onDisk(projectPath, output)))) {
13425
13475
  return "its imported output is not on disk";
13426
13476
  }
@@ -13428,7 +13478,7 @@ function behind(projectPath, resPath, sidecar) {
13428
13478
  const record = join12(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
13429
13479
  let recorded = "";
13430
13480
  try {
13431
- recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync9(record, "utf8"))?.[1] ?? "";
13481
+ recorded = /^source_md5="([0-9a-f]*)"/m.exec(readFileSync10(record, "utf8"))?.[1] ?? "";
13432
13482
  } catch {
13433
13483
  recorded = "";
13434
13484
  }
@@ -13470,7 +13520,7 @@ function staleImports(projectPath) {
13470
13520
  }
13471
13521
  let sidecar;
13472
13522
  try {
13473
- sidecar = readFileSync9(join12(directory, entry.name), "utf8");
13523
+ sidecar = readFileSync10(join12(directory, entry.name), "utf8");
13474
13524
  } catch {
13475
13525
  continue;
13476
13526
  }
@@ -13534,7 +13584,7 @@ import {
13534
13584
  existsSync as existsSync11,
13535
13585
  mkdirSync as mkdirSync4,
13536
13586
  readdirSync as readdirSync8,
13537
- readFileSync as readFileSync10,
13587
+ readFileSync as readFileSync11,
13538
13588
  renameSync as renameSync2,
13539
13589
  unlinkSync,
13540
13590
  writeFileSync as writeFileSync6
@@ -13650,7 +13700,7 @@ function resolveResource(projectPath, relativeTo, path) {
13650
13700
  }
13651
13701
  function readOrNull(path) {
13652
13702
  try {
13653
- return readFileSync10(path, "utf8");
13703
+ return readFileSync11(path, "utf8");
13654
13704
  } catch {
13655
13705
  return null;
13656
13706
  }
@@ -13659,7 +13709,7 @@ var init_library_copies = () => {};
13659
13709
 
13660
13710
  // src/reimport.ts
13661
13711
  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";
13712
+ import { existsSync as existsSync12, readFileSync as readFileSync12, rmSync as rmSync7, statSync as statSync11, utimesSync, writeFileSync as writeFileSync7 } from "node:fs";
13663
13713
  import { basename as basename7, join as join14 } from "node:path";
13664
13714
  function forceNextImport(projectPath, resourcePath) {
13665
13715
  const relative = resourcePath.replace(/^res:\/\//, "");
@@ -13668,7 +13718,7 @@ function forceNextImport(projectPath, resourcePath) {
13668
13718
  return;
13669
13719
  }
13670
13720
  const { atime, mtimeMs } = statSync11(sidecar);
13671
- writeFileSync7(sidecar, readFileSync11(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
13721
+ writeFileSync7(sidecar, readFileSync12(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
13672
13722
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
13673
13723
  rmSync7(importRecord(projectPath, resourcePath), { force: true });
13674
13724
  }
@@ -13894,7 +13944,7 @@ var init_script_origins = __esm(() => {
13894
13944
  });
13895
13945
 
13896
13946
  // src/headless.ts
13897
- import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "node:fs";
13947
+ import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
13898
13948
  import { dirname as dirname7, join as join17 } from "node:path";
13899
13949
  function snakeCased(params) {
13900
13950
  const result = emptyRecord();
@@ -14185,7 +14235,7 @@ function answerIn(file) {
14185
14235
  if (!existsSync14(file)) {
14186
14236
  return { kind: "none" };
14187
14237
  }
14188
- const text = readFileSync12(file, "utf8");
14238
+ const text = readFileSync13(file, "utf8");
14189
14239
  try {
14190
14240
  const parsed = JSON.parse(text);
14191
14241
  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,7 +14260,7 @@ var init_headless = __esm(() => {
14210
14260
  });
14211
14261
 
14212
14262
  // src/update-check.ts
14213
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
14263
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "node:fs";
14214
14264
  import { homedir as homedir5, tmpdir as tmpdir4 } from "node:os";
14215
14265
  import { join as join18 } from "node:path";
14216
14266
  function packageUrl(root) {
@@ -14255,7 +14305,7 @@ function readCache(path) {
14255
14305
  if (!existsSync15(path)) {
14256
14306
  return null;
14257
14307
  }
14258
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
14308
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
14259
14309
  if (typeof parsed !== "object" || parsed === null) {
14260
14310
  return null;
14261
14311
  }
@@ -14403,7 +14453,7 @@ var init_update_check = __esm(() => {
14403
14453
  });
14404
14454
 
14405
14455
  // src/server-version.ts
14406
- import { readFileSync as readFileSync14 } from "node:fs";
14456
+ import { readFileSync as readFileSync15 } from "node:fs";
14407
14457
  function staleAddon(addonDigest, shippedDigest) {
14408
14458
  return addonDigest !== shippedDigest;
14409
14459
  }
@@ -14462,7 +14512,7 @@ var init_server_version = __esm(() => {
14462
14512
  DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
14463
14513
  SERVER_VERSION = (() => {
14464
14514
  try {
14465
- const pkg = JSON.parse(readFileSync14(new URL("../package.json", import.meta.url), "utf8"));
14515
+ const pkg = JSON.parse(readFileSync15(new URL("../package.json", import.meta.url), "utf8"));
14466
14516
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
14467
14517
  } catch {
14468
14518
  return "0.0.0";
@@ -15573,7 +15623,7 @@ var init_lsp_client = __esm(() => {
15573
15623
  });
15574
15624
 
15575
15625
  // src/resources.ts
15576
- import { readFileSync as readFileSync16, realpathSync as realpathSync3 } from "node:fs";
15626
+ import { readFileSync as readFileSync17, realpathSync as realpathSync3 } from "node:fs";
15577
15627
  import { extname as extname2, resolve as resolve8 } from "node:path";
15578
15628
  function ensureProjectPath(getProjectPath) {
15579
15629
  const projectPath = getProjectPath();
@@ -15769,7 +15819,7 @@ function readResourceText(uri, getProjectPath) {
15769
15819
  const parsedUri = parseGodotUri(uri);
15770
15820
  if (parsedUri.kind === "project-info") {
15771
15821
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
15772
- const rawProject = readFileSync16(projectFilePath, "utf-8");
15822
+ const rawProject = readFileSync17(projectFilePath, "utf-8");
15773
15823
  const parsedProject = parseProjectGodot(rawProject);
15774
15824
  return {
15775
15825
  mimeType: "application/json",
@@ -15778,7 +15828,7 @@ function readResourceText(uri, getProjectPath) {
15778
15828
  }
15779
15829
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
15780
15830
  ensureAllowedExtension(parsedUri.kind, filePath);
15781
- const text = readFileSync16(filePath, "utf-8");
15831
+ const text = readFileSync17(filePath, "utf-8");
15782
15832
  return {
15783
15833
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
15784
15834
  text
@@ -15925,7 +15975,7 @@ import {
15925
15975
  existsSync as existsSync18,
15926
15976
  mkdirSync as mkdirSync6,
15927
15977
  readdirSync as readdirSync11,
15928
- readFileSync as readFileSync17,
15978
+ readFileSync as readFileSync18,
15929
15979
  rmdirSync,
15930
15980
  rmSync as rmSync9,
15931
15981
  statSync as statSync12,
@@ -15959,7 +16009,7 @@ function addonsDigest(names, from) {
15959
16009
  } else {
15960
16010
  hash.update(`${inside}
15961
16011
  `);
15962
- hash.update(readFileSync17(path));
16012
+ hash.update(readFileSync18(path));
15963
16013
  hash.update(`
15964
16014
  `);
15965
16015
  }
@@ -15989,7 +16039,7 @@ function digestMarker(projectPath, addon) {
15989
16039
  if (!existsSync18(marker)) {
15990
16040
  return null;
15991
16041
  }
15992
- const said = readFileSync17(marker, "utf8").trim();
16042
+ const said = readFileSync18(marker, "utf8").trim();
15993
16043
  return said === "" ? null : said;
15994
16044
  }
15995
16045
  function shippedAddonsDirectory() {
@@ -16006,7 +16056,7 @@ function loaderState(projectPath) {
16006
16056
  if (!existsSync18(copy)) {
16007
16057
  return "missing";
16008
16058
  }
16009
- const lines = (path) => readFileSync17(path, "utf8").replaceAll(`\r
16059
+ const lines = (path) => readFileSync18(path, "utf8").replaceAll(`\r
16010
16060
  `, `
16011
16061
  `);
16012
16062
  return lines(copy) === lines(shippedLoader()) ? "current" : "differs";
@@ -16017,7 +16067,7 @@ function writeLoader(projectPath) {
16017
16067
  }
16018
16068
  const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
16019
16069
  mkdirSync6(dirname9(copy), { recursive: true });
16020
- writeFileSync10(copy, readFileSync17(shippedLoader()));
16070
+ writeFileSync10(copy, readFileSync18(shippedLoader()));
16021
16071
  return true;
16022
16072
  }
16023
16073
  function removeLoader(projectPath) {
@@ -16076,7 +16126,7 @@ function installedAddonVersion(projectPath) {
16076
16126
  if (!existsSync18(marker)) {
16077
16127
  return null;
16078
16128
  }
16079
- const said = readFileSync17(marker, "utf8").trim();
16129
+ const said = readFileSync18(marker, "utf8").trim();
16080
16130
  return said === "" ? null : said;
16081
16131
  }
16082
16132
  function removeAddons(projectPath) {
@@ -16131,7 +16181,7 @@ function loaderAmong(projectPath, named) {
16131
16181
  }
16132
16182
  let source = null;
16133
16183
  try {
16134
- source = readFileSync17(join21(projectPath, path), "utf8");
16184
+ source = readFileSync18(join21(projectPath, path), "utf8");
16135
16185
  } catch {}
16136
16186
  if (source?.includes(RUNTIME_SCRIPT)) {
16137
16187
  return { name: entry, path, exists: true };
@@ -16190,7 +16240,7 @@ function inspectProject(projectPath) {
16190
16240
  const directory = join21(projectPath, "addons", name);
16191
16241
  const marker = join21(directory, VERSION_MARKER);
16192
16242
  const installed = existsSync18(directory);
16193
- const version = existsSync18(marker) ? readFileSync17(marker, "utf8").trim() : null;
16243
+ const version = existsSync18(marker) ? readFileSync18(marker, "utf8").trim() : null;
16194
16244
  const current = version === SERVER_VERSION;
16195
16245
  if (!installed) {
16196
16246
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -16199,7 +16249,7 @@ function inspectProject(projectPath) {
16199
16249
  }
16200
16250
  return { name, installed, version, current };
16201
16251
  });
16202
- const settings = parseProjectGodot(readFileSync17(join21(projectPath, "project.godot"), "utf8"));
16252
+ const settings = parseProjectGodot(readFileSync18(join21(projectPath, "project.godot"), "utf8"));
16203
16253
  const pluginsEnabled = enabledPlugins(settings);
16204
16254
  for (const name of EDITOR_PLUGINS) {
16205
16255
  if (!pluginsEnabled.includes(name)) {
@@ -16630,7 +16680,11 @@ var init_tool_definitions = __esm(() => {
16630
16680
  ops: ["set"],
16631
16681
  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
16682
  },
16633
- name: { type: "string", ops: ["add_autoload", "remove_autoload"], description: "Autoload name." },
16683
+ name: {
16684
+ type: "string",
16685
+ ops: ["add_autoload", "remove_autoload"],
16686
+ 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."
16687
+ },
16634
16688
  path: {
16635
16689
  type: "string",
16636
16690
  ops: ["add_autoload"],
@@ -16784,7 +16838,7 @@ var init_tool_definitions = __esm(() => {
16784
16838
  maxResults: {
16785
16839
  type: "integer",
16786
16840
  minimum: 1,
16787
- description: "How many matching lines to answer. Default 100."
16841
+ description: "How many matching lines to answer. Default 100. Past it truncated is true, and the summary still counts the whole search."
16788
16842
  }
16789
16843
  },
16790
16844
  requires: ["projectPath", "query"]
@@ -16823,7 +16877,7 @@ var init_tool_definitions = __esm(() => {
16823
16877
  name: "project_import",
16824
16878
  title: "Asset imports",
16825
16879
  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.",
16880
+ 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
16881
  parameters: {
16828
16882
  projectPath: PROJECT_PATH,
16829
16883
  resourcePath: { ...RESOURCE_PATH, ops: ["status", "options", "set_options", "uid", "reimport"] },
@@ -16885,7 +16939,7 @@ var init_tool_definitions = __esm(() => {
16885
16939
  },
16886
16940
  refresh_classes: {
16887
16941
  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',
16942
+ 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
16943
  requires: []
16890
16944
  }
16891
16945
  }
@@ -16893,7 +16947,7 @@ var init_tool_definitions = __esm(() => {
16893
16947
  {
16894
16948
  name: "project_export",
16895
16949
  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.",
16950
+ 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
16951
  parameters: {
16898
16952
  projectPath: PROJECT_PATH,
16899
16953
  preset: {
@@ -16977,7 +17031,7 @@ var init_tool_definitions = __esm(() => {
16977
17031
  title: "Run tests",
16978
17032
  effect: "additive",
16979
17033
  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,
17034
+ 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
17035
  parameters: {
16982
17036
  projectPath: PROJECT_PATH,
16983
17037
  path: {
@@ -17512,7 +17566,7 @@ var init_tool_definitions = __esm(() => {
17512
17566
  name: "script_diagnostics",
17513
17567
  title: "Script diagnostics",
17514
17568
  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.",
17569
+ 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
17570
  parameters: {
17517
17571
  projectPath: PROJECT_PATH,
17518
17572
  scriptPath: {
@@ -17899,7 +17953,8 @@ var init_tool_definitions = __esm(() => {
17899
17953
  name: "editor_rescan",
17900
17954
  title: "Rescan the project",
17901
17955
  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.",
17956
+ 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.",
17957
+ 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
17958
  parameters: {
17904
17959
  projectPath: PROJECT_PATH,
17905
17960
  timeoutMs: {
@@ -18557,7 +18612,7 @@ var init_tool_definitions = __esm(() => {
18557
18612
  {
18558
18613
  name: "debug_state",
18559
18614
  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.",
18615
+ 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
18616
  parameters: {
18562
18617
  frameId: {
18563
18618
  type: "number",
@@ -18698,9 +18753,9 @@ function whatItCanChange(tool) {
18698
18753
  if (destroying.length === 0) {
18699
18754
  return "Adds or starts things, and overwrites and removes nothing itself.";
18700
18755
  }
18701
- return `Can overwrite, remove or end what is there, through ${listed2(destroying)}.`;
18756
+ return `Can overwrite, remove or end what is there, through ${listed3(destroying)}.`;
18702
18757
  }
18703
- function listed2(items) {
18758
+ function listed3(items) {
18704
18759
  const last = items.at(-1) ?? "";
18705
18760
  return items.length === 1 ? last : `${items.slice(0, -1).join(", ")} and ${last}`;
18706
18761
  }
@@ -18758,7 +18813,7 @@ var init_tool_reference = __esm(() => {
18758
18813
  });
18759
18814
 
18760
18815
  // src/suite-plan.ts
18761
- import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync20 } from "node:fs";
18816
+ import { existsSync as existsSync22, mkdirSync as mkdirSync9, readFileSync as readFileSync21 } from "node:fs";
18762
18817
  import { dirname as dirname11, join as join25, posix as posix3 } from "node:path";
18763
18818
  function within(path, folder) {
18764
18819
  return path === folder || path.startsWith(`${folder}/`);
@@ -18775,7 +18830,7 @@ class ProjectScripts {
18775
18830
  this.projectPath = projectPath;
18776
18831
  let settings = {};
18777
18832
  try {
18778
- settings = parseProjectGodot(readFileSync20(join25(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18833
+ settings = parseProjectGodot(readFileSync21(join25(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18779
18834
  } catch {}
18780
18835
  const [section, key] = [NOT_SEARCHED_SETTING, GUESS_WINDOW_SETTING].map((setting) => setting.slice("gdharness/".length));
18781
18836
  const listed = settings[section ?? ""];
@@ -18890,7 +18945,7 @@ class ProjectScripts {
18890
18945
  }
18891
18946
  function readTimes(file) {
18892
18947
  try {
18893
- const parsed = JSON.parse(readFileSync20(file, "utf8"));
18948
+ const parsed = JSON.parse(readFileSync21(file, "utf8"));
18894
18949
  return typeof parsed === "object" && parsed !== null ? parsed : {};
18895
18950
  } catch {
18896
18951
  return {};
@@ -19173,7 +19228,7 @@ function viewportOf(projectPath) {
19173
19228
  if (!existsSync22(file)) {
19174
19229
  return null;
19175
19230
  }
19176
- const text = readFileSync20(file, "utf8");
19231
+ const text = readFileSync21(file, "utf8");
19177
19232
  const width = /^window\/size\/viewport_width\s*=\s*(\d+)/m.exec(text)?.[1];
19178
19233
  const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
19179
19234
  return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
@@ -40792,13 +40847,13 @@ var init_stdio = __esm(() => {
40792
40847
  });
40793
40848
 
40794
40849
  // src/autoload-failure.ts
40795
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
40850
+ import { existsSync as existsSync24, readFileSync as readFileSync24 } from "node:fs";
40796
40851
  import { join as join28 } from "node:path";
40797
40852
  function scriptsAutoloaded(projectPath) {
40798
40853
  const scripts = new Map;
40799
40854
  let settings;
40800
40855
  try {
40801
- settings = parseProjectGodot(readFileSync23(join28(projectPath, "project.godot"), "utf8"));
40856
+ settings = parseProjectGodot(readFileSync24(join28(projectPath, "project.godot"), "utf8"));
40802
40857
  } catch {
40803
40858
  return scripts;
40804
40859
  }
@@ -40812,7 +40867,7 @@ function scriptsAutoloaded(projectPath) {
40812
40867
  continue;
40813
40868
  }
40814
40869
  try {
40815
- for (const named of readFileSync23(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
40870
+ for (const named of readFileSync24(scene, "utf8").matchAll(/^\[ext_resource\b[^\]]*\btype="Script"[^\]]*\bpath="(res:\/\/[^"]+)"/gm)) {
40816
40871
  scripts.set(named[1] ?? "", name);
40817
40872
  }
40818
40873
  } catch {}
@@ -40831,7 +40886,7 @@ var init_autoload_failure = __esm(() => {
40831
40886
  });
40832
40887
 
40833
40888
  // src/runtime-client.ts
40834
- import { existsSync as existsSync25, readdirSync as readdirSync15, readFileSync as readFileSync24, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
40889
+ import { existsSync as existsSync25, readdirSync as readdirSync15, readFileSync as readFileSync25, statSync as statSync15, unlinkSync as unlinkSync3 } from "node:fs";
40835
40890
  import { createConnection as createConnection2 } from "node:net";
40836
40891
  import { tmpdir as tmpdir5 } from "node:os";
40837
40892
  import { dirname as dirname14, join as join29, resolve as resolve11 } from "node:path";
@@ -40886,7 +40941,7 @@ function reportAlreadyInTranscript(transcript, report) {
40886
40941
  function parseAnnouncement(file, pid) {
40887
40942
  let fields;
40888
40943
  try {
40889
- fields = asParams(JSON.parse(readFileSync24(file, "utf8")));
40944
+ fields = asParams(JSON.parse(readFileSync25(file, "utf8")));
40890
40945
  } catch {
40891
40946
  return { kind: "rubbish" };
40892
40947
  }
@@ -40958,7 +41013,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
40958
41013
  function wentAway(file, pid) {
40959
41014
  let project = "";
40960
41015
  try {
40961
- const fields = asParams(JSON.parse(readFileSync24(file, "utf8")));
41016
+ const fields = asParams(JSON.parse(readFileSync25(file, "utf8")));
40962
41017
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
40963
41018
  } catch {
40964
41019
  return;
@@ -41473,14 +41528,14 @@ var init_runtime_client = __esm(() => {
41473
41528
  });
41474
41529
 
41475
41530
  // src/boot-note.ts
41476
- import { mkdirSync as mkdirSync11, readFileSync as readFileSync25, writeFileSync as writeFileSync13 } from "node:fs";
41531
+ import { mkdirSync as mkdirSync11, readFileSync as readFileSync26, writeFileSync as writeFileSync13 } from "node:fs";
41477
41532
  import { dirname as dirname15, join as join30 } from "node:path";
41478
41533
  function bootNotePath(projectPath) {
41479
41534
  return join30(projectPath, ".godot", "gdharness-boot.json");
41480
41535
  }
41481
41536
  function readScenes(projectPath) {
41482
41537
  try {
41483
- return asParams(asParams(JSON.parse(readFileSync25(bootNotePath(projectPath), "utf8")))["scenes"]);
41538
+ return asParams(asParams(JSON.parse(readFileSync26(bootNotePath(projectPath), "utf8")))["scenes"]);
41484
41539
  } catch {
41485
41540
  return {};
41486
41541
  }
@@ -41522,7 +41577,7 @@ var init_boot_note = __esm(() => {
41522
41577
  });
41523
41578
 
41524
41579
  // src/breakpoint-note.ts
41525
- import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, rmSync as rmSync13, writeFileSync as writeFileSync14 } from "node:fs";
41580
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync27, rmSync as rmSync13, writeFileSync as writeFileSync14 } from "node:fs";
41526
41581
  import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as relative6 } from "node:path";
41527
41582
  function breakpointNotePath(projectPath) {
41528
41583
  return join31(projectPath, ".godot", "gdharness-breakpoints.json");
@@ -41551,7 +41606,7 @@ function writeBreakpointNote(projectPath, held) {
41551
41606
  }
41552
41607
  function readBreakpointNote(projectPath) {
41553
41608
  try {
41554
- const parsed = JSON.parse(readFileSync26(breakpointNotePath(projectPath), "utf8"));
41609
+ const parsed = JSON.parse(readFileSync27(breakpointNotePath(projectPath), "utf8"));
41555
41610
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
41556
41611
  return [];
41557
41612
  }
@@ -41584,14 +41639,14 @@ var init_breakpoint_note = __esm(() => {
41584
41639
  });
41585
41640
 
41586
41641
  // src/class-note.ts
41587
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync27 } from "node:fs";
41642
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync28 } from "node:fs";
41588
41643
  import { dirname as dirname17, join as join32 } from "node:path";
41589
41644
  function classNotePath(projectPath) {
41590
41645
  return join32(projectPath, ".godot", "gdharness-classes.json");
41591
41646
  }
41592
41647
  function readClassNote(projectPath) {
41593
41648
  try {
41594
- const fields = asParams(JSON.parse(readFileSync27(classNotePath(projectPath), "utf8")));
41649
+ const fields = asParams(JSON.parse(readFileSync28(classNotePath(projectPath), "utf8")));
41595
41650
  const rebuiltWith = readArray(fields, "rebuiltWith");
41596
41651
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
41597
41652
  return null;
@@ -41633,7 +41688,7 @@ var init_class_note = __esm(() => {
41633
41688
  });
41634
41689
 
41635
41690
  // src/config-pin.ts
41636
- import { existsSync as existsSync26, readFileSync as readFileSync28 } from "node:fs";
41691
+ import { existsSync as existsSync26, readFileSync as readFileSync29 } from "node:fs";
41637
41692
  import { join as join33 } from "node:path";
41638
41693
  function versionsNamedInConfig(projectPath) {
41639
41694
  const config = join33(projectPath, ".mcp.json");
@@ -41642,7 +41697,7 @@ function versionsNamedInConfig(projectPath) {
41642
41697
  }
41643
41698
  let text;
41644
41699
  try {
41645
- text = readFileSync28(config, "utf8");
41700
+ text = readFileSync29(config, "utf8");
41646
41701
  JSON.parse(text);
41647
41702
  } catch {
41648
41703
  return [];
@@ -41663,7 +41718,7 @@ var init_config_pin = __esm(() => {
41663
41718
  });
41664
41719
 
41665
41720
  // 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";
41721
+ import { existsSync as existsSync27, mkdirSync as mkdirSync14, readFileSync as readFileSync30, rmSync as rmSync14, writeFileSync as writeFileSync15 } from "node:fs";
41667
41722
  import { dirname as dirname18, join as join34 } from "node:path";
41668
41723
  function keptAt(projectPath) {
41669
41724
  return join34(projectPath, ".godot", "gdharness-reports", "cut-run.json");
@@ -41682,7 +41737,7 @@ function takeCutTestRun(projectPath) {
41682
41737
  return null;
41683
41738
  }
41684
41739
  try {
41685
- const run = JSON.parse(readFileSync29(path, "utf8"));
41740
+ const run = JSON.parse(readFileSync30(path, "utf8"));
41686
41741
  rmSync14(path, { force: true });
41687
41742
  return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
41688
41743
  } catch {
@@ -42418,7 +42473,7 @@ import {
42418
42473
  existsSync as existsSync28,
42419
42474
  mkdirSync as mkdirSync15,
42420
42475
  openSync as openSync4,
42421
- readFileSync as readFileSync30,
42476
+ readFileSync as readFileSync31,
42422
42477
  readSync as readSync2,
42423
42478
  rmSync as rmSync15,
42424
42479
  statSync as statSync16,
@@ -42433,7 +42488,7 @@ function editorLogNotePath(projectPath) {
42433
42488
  }
42434
42489
  function readEditorLogNote(projectPath) {
42435
42490
  try {
42436
- const fields = asParams(JSON.parse(readFileSync30(editorLogNotePath(projectPath), "utf8")));
42491
+ const fields = asParams(JSON.parse(readFileSync31(editorLogNotePath(projectPath), "utf8")));
42437
42492
  const pid = readNumber(fields, "pid");
42438
42493
  const startedAt = readString(fields, "startedAt");
42439
42494
  const processStartedAt = readNumber(fields, "processStartedAt");
@@ -42537,7 +42592,7 @@ function foldProgress(text) {
42537
42592
  function theFirstOf(path) {
42538
42593
  const whole = statSync16(path).size;
42539
42594
  if (whole <= MOST_CONSOLE_BYTES) {
42540
- return { text: readFileSync30(path, "utf8") };
42595
+ return { text: readFileSync31(path, "utf8") };
42541
42596
  }
42542
42597
  const handle = openSync4(path, "r");
42543
42598
  try {
@@ -42698,7 +42753,7 @@ var init_editor_log = __esm(() => {
42698
42753
  });
42699
42754
 
42700
42755
  // 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";
42756
+ import { existsSync as existsSync29, mkdirSync as mkdirSync16, readFileSync as readFileSync32, renameSync as renameSync3, unlinkSync as unlinkSync4, writeFileSync as writeFileSync17 } from "node:fs";
42702
42757
  import { dirname as dirname20, posix as posix4 } from "node:path";
42703
42758
 
42704
42759
  class Script {
@@ -43403,7 +43458,7 @@ function rewritesOf(plan, texts, move) {
43403
43458
  }
43404
43459
  function applyRewrites(rewrites) {
43405
43460
  for (const rewrite of rewrites) {
43406
- const now = readFileSync31(rewrite.absolute, "utf8");
43461
+ const now = readFileSync32(rewrite.absolute, "utf8");
43407
43462
  if (now !== rewrite.before) {
43408
43463
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
43409
43464
  }
@@ -43493,18 +43548,18 @@ function onDiskSpelling(path) {
43493
43548
  }
43494
43549
  }
43495
43550
  function usedAsIdentifier(scripts, name, most) {
43496
- const found = [];
43551
+ const found = new Set;
43497
43552
  for (const script of scripts.values()) {
43498
43553
  for (const occurrence of script.occurrences(name)) {
43499
43554
  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;
43555
+ found.add(`${script.path}:${script.lines.positionOf(occurrence.offset).line + 1}`);
43556
+ if (found.size >= most) {
43557
+ return [...found];
43503
43558
  }
43504
43559
  }
43505
43560
  }
43506
43561
  }
43507
- return found;
43562
+ return [...found];
43508
43563
  }
43509
43564
  function autoloadNames(texts) {
43510
43565
  const settings = texts.find((file) => file.path === "res://project.godot");
@@ -43568,7 +43623,7 @@ async function renameSymbol(request, services) {
43568
43623
  conflicts.push(`${newName} is already an autoload in project.godot`);
43569
43624
  }
43570
43625
  for (const place of usedAsIdentifier(scripts, newName, 10)) {
43571
- conflicts.push(`${newName} is already an identifier at ${place}`);
43626
+ conflicts.push(`${newName} is already used in code at ${place}`);
43572
43627
  }
43573
43628
  const taken = (await services.namesTaken([newName], null))[newName];
43574
43629
  if (taken !== undefined && taken.as.length > 0) {
@@ -43971,7 +44026,7 @@ import {
43971
44026
  existsSync as existsSync31,
43972
44027
  mkdirSync as mkdirSync18,
43973
44028
  readdirSync as readdirSync17,
43974
- readFileSync as readFileSync32,
44029
+ readFileSync as readFileSync33,
43975
44030
  renameSync as renameSync4,
43976
44031
  statSync as statSync17,
43977
44032
  writeFileSync as writeFileSync19
@@ -44131,7 +44186,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44131
44186
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
44132
44187
  const binaries = [];
44133
44188
  for (const path of binaryResources(projectPath)) {
44134
- const bytes = readFileSync32(join37(projectPath, path.slice("res://".length)));
44189
+ const bytes = readFileSync33(join37(projectPath, path.slice("res://".length)));
44135
44190
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
44136
44191
  if (names !== undefined) {
44137
44192
  binaries.push({
@@ -44192,7 +44247,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44192
44247
  }
44193
44248
  function applyFileMove(outcome) {
44194
44249
  for (const rewrite of outcome.inPlace) {
44195
- if (readFileSync32(rewrite.absolute, "utf8") !== rewrite.before) {
44250
+ if (readFileSync33(rewrite.absolute, "utf8") !== rewrite.before) {
44196
44251
  throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
44197
44252
  }
44198
44253
  }
@@ -44200,7 +44255,7 @@ function applyFileMove(outcome) {
44200
44255
  if (!existsSync31(file.absoluteFrom)) {
44201
44256
  throw new Error(`${file.from} is gone from disk; nothing was written`);
44202
44257
  }
44203
- if (file.text !== null && readFileSync32(file.absoluteFrom, "utf8") !== file.text.before) {
44258
+ if (file.text !== null && readFileSync33(file.absoluteFrom, "utf8") !== file.text.before) {
44204
44259
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
44205
44260
  }
44206
44261
  if (existsSync31(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
@@ -48314,7 +48369,7 @@ var init_prompts = __esm(() => {
48314
48369
  });
48315
48370
 
48316
48371
  // src/restart-note.ts
48317
- import { mkdirSync as mkdirSync20, readFileSync as readFileSync33, rmSync as rmSync17, writeFileSync as writeFileSync20 } from "node:fs";
48372
+ import { mkdirSync as mkdirSync20, readFileSync as readFileSync34, rmSync as rmSync17, writeFileSync as writeFileSync20 } from "node:fs";
48318
48373
  import { dirname as dirname24, join as join39 } from "node:path";
48319
48374
  function restartNotePath(projectPath) {
48320
48375
  return join39(projectPath, ".godot", "gdharness-restart.json");
@@ -48331,7 +48386,7 @@ function noteRestartBegun(note) {
48331
48386
  }
48332
48387
  function restartOwed(projectPath) {
48333
48388
  try {
48334
- const parsed = JSON.parse(readFileSync33(restartNotePath(projectPath), "utf8"));
48389
+ const parsed = JSON.parse(readFileSync34(restartNotePath(projectPath), "utf8"));
48335
48390
  if (typeof parsed !== "object" || parsed === null) {
48336
48391
  return null;
48337
48392
  }
@@ -48357,7 +48412,7 @@ var init_restart_note = __esm(() => {
48357
48412
 
48358
48413
  // src/run-record.ts
48359
48414
  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";
48415
+ import { mkdirSync as mkdirSync21, openSync as openSync5, readdirSync as readdirSync18, readFileSync as readFileSync35, rmSync as rmSync18, statSync as statSync19 } from "node:fs";
48361
48416
  import { basename as basename11, join as join40, resolve as resolve13 } from "node:path";
48362
48417
  function runsDirectory() {
48363
48418
  return join40(runtimeDirectory(), "runs");
@@ -48381,7 +48436,7 @@ function writeLatestRun(servedBy, latest) {
48381
48436
  }
48382
48437
  function readLatestRun(servedBy, directory = runtimeDirectory()) {
48383
48438
  try {
48384
- const fields = JSON.parse(readFileSync34(latestRunPath(servedBy, directory), "utf8"));
48439
+ const fields = JSON.parse(readFileSync35(latestRunPath(servedBy, directory), "utf8"));
48385
48440
  if (typeof fields !== "object" || fields === null) {
48386
48441
  return null;
48387
48442
  }
@@ -48430,7 +48485,7 @@ function readEditorRunNote(projectPath) {
48430
48485
  function editorNoteAt(path) {
48431
48486
  let parsed;
48432
48487
  try {
48433
- parsed = JSON.parse(readFileSync34(path, "utf8"));
48488
+ parsed = JSON.parse(readFileSync35(path, "utf8"));
48434
48489
  } catch {
48435
48490
  return null;
48436
48491
  }
@@ -48480,7 +48535,7 @@ function everyRunRecord() {
48480
48535
  function recordAt(path) {
48481
48536
  let parsed;
48482
48537
  try {
48483
- parsed = JSON.parse(readFileSync34(path, "utf8"));
48538
+ parsed = JSON.parse(readFileSync35(path, "utf8"));
48484
48539
  } catch {
48485
48540
  return null;
48486
48541
  }
@@ -48561,7 +48616,7 @@ async function runningAs(pid) {
48561
48616
  const began = await startedAt(pid, null);
48562
48617
  const when = began === null ? {} : { startedAt: began };
48563
48618
  if (process.platform === "linux") {
48564
- const raw = readFileSync34(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
48619
+ const raw = readFileSync35(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
48565
48620
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
48566
48621
  }
48567
48622
  const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
@@ -48757,10 +48812,10 @@ var init_run_record = __esm(() => {
48757
48812
  });
48758
48813
 
48759
48814
  // src/script-profile.ts
48760
- import { readFileSync as readFileSync35 } from "node:fs";
48815
+ import { readFileSync as readFileSync36 } from "node:fs";
48761
48816
  function readProfileFile(path) {
48762
48817
  try {
48763
- const parsed = JSON.parse(readFileSync35(path, "utf8"));
48818
+ const parsed = JSON.parse(readFileSync36(path, "utf8"));
48764
48819
  return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
48765
48820
  } catch {
48766
48821
  return null;
@@ -48818,7 +48873,7 @@ function writeProfileMark(path, mark) {
48818
48873
  }
48819
48874
  function readProfileMark(path) {
48820
48875
  try {
48821
- const parsed = JSON.parse(readFileSync35(path, "utf8"));
48876
+ const parsed = JSON.parse(readFileSync36(path, "utf8"));
48822
48877
  return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
48823
48878
  } catch {
48824
48879
  return null;
@@ -49150,7 +49205,7 @@ import {
49150
49205
  mkdirSync as mkdirSync23,
49151
49206
  openSync as openSync6,
49152
49207
  readdirSync as readdirSync19,
49153
- readFileSync as readFileSync36,
49208
+ readFileSync as readFileSync37,
49154
49209
  readSync as readSync3,
49155
49210
  realpathSync as realpathSync5,
49156
49211
  rmdirSync as rmdirSync2,
@@ -49171,9 +49226,9 @@ function greetingWaitMs() {
49171
49226
  return waitFromEnv("GDHARNESS_GREETING_WAIT_MS", 1e4);
49172
49227
  }
49173
49228
  function pictureToSend(pngPath, jpegPath) {
49174
- const png = readFileSync36(pngPath);
49229
+ const png = readFileSync37(pngPath);
49175
49230
  const beside = pngPath.replace(/\.png$/, ".jpg");
49176
- const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ? readFileSync36(beside) : null;
49231
+ const jpeg = jpegPath !== undefined && normalize2(jpegPath) === normalize2(beside) && existsSync34(beside) ? readFileSync37(beside) : null;
49177
49232
  const sent = jpeg !== null && jpeg.length < png.length ? jpeg : png;
49178
49233
  return {
49179
49234
  data: sent.toString("base64"),
@@ -49652,7 +49707,7 @@ function endedToStartThis(ended) {
49652
49707
  return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
49653
49708
  }
49654
49709
  function lineNothingStopsOn(absolutePath, relativePath, line) {
49655
- const text = readFileSync36(absolutePath, "utf8");
49710
+ const text = readFileSync37(absolutePath, "utf8");
49656
49711
  const lines = text === "" ? 0 : text.split(`
49657
49712
  `).length - (text.endsWith(`
49658
49713
  `) ? 1 : 0);
@@ -49863,7 +49918,7 @@ function notRunNote(notRun, failFast) {
49863
49918
  function presetsIn(projectPath) {
49864
49919
  let text;
49865
49920
  try {
49866
- text = readFileSync36(join43(projectPath, "export_presets.cfg"), "utf8");
49921
+ text = readFileSync37(join43(projectPath, "export_presets.cfg"), "utf8");
49867
49922
  } catch {
49868
49923
  return null;
49869
49924
  }
@@ -50013,7 +50068,7 @@ function rewriteBusNamed(projectPath, from, to) {
50013
50068
  const rewritten = [];
50014
50069
  for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
50015
50070
  const absolute = join43(projectPath, file);
50016
- const lines = readFileSync36(absolute, "utf8").split(`
50071
+ const lines = readFileSync37(absolute, "utf8").split(`
50017
50072
  `);
50018
50073
  const at = [];
50019
50074
  const after = lines.map((line, index) => {
@@ -50032,7 +50087,7 @@ function rewriteBusNamed(projectPath, from, to) {
50032
50087
  }
50033
50088
  const namedInScripts = [];
50034
50089
  for (const file of projectFiles(projectPath, [".gd"])) {
50035
- readFileSync36(join43(projectPath, file), "utf8").split(`
50090
+ readFileSync37(join43(projectPath, file), "utf8").split(`
50036
50091
  `).forEach((line, index) => {
50037
50092
  if (line.includes(`"${from}"`)) {
50038
50093
  namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
@@ -50317,7 +50372,7 @@ function profileFrameFunctions(projectPath) {
50317
50372
  let projectGodot = null;
50318
50373
  if (projectPath !== null) {
50319
50374
  try {
50320
- projectGodot = readFileSync36(join43(projectPath, "project.godot"), "utf8");
50375
+ projectGodot = readFileSync37(join43(projectPath, "project.godot"), "utf8");
50321
50376
  } catch {}
50322
50377
  }
50323
50378
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -50341,7 +50396,7 @@ function projectSourceOf(projectPath) {
50341
50396
  let source = null;
50342
50397
  if (inside.ok) {
50343
50398
  try {
50344
- source = readFileSync36(inside.absolutePath, "utf8");
50399
+ source = readFileSync37(inside.absolutePath, "utf8");
50345
50400
  } catch {}
50346
50401
  }
50347
50402
  read.set(script, source);
@@ -50513,7 +50568,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
50513
50568
  }
50514
50569
  function gdUnitFailsOnScriptErrors(projectPath) {
50515
50570
  try {
50516
- return settingKeys(readFileSync36(join43(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
50571
+ return settingKeys(readFileSync37(join43(projectPath, "project.godot"), "utf8")).get("gdunit4/report/godot/script_error") !== false;
50517
50572
  } catch {
50518
50573
  return true;
50519
50574
  }
@@ -50645,7 +50700,7 @@ function engineChecked(why) {
50645
50700
  return {
50646
50701
  checkedBy: "engine",
50647
50702
  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."
50703
+ 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
50704
  };
50650
50705
  }
50651
50706
  function asWarning(raised) {
@@ -50660,11 +50715,11 @@ function isDirectory(path) {
50660
50715
  }
50661
50716
  }
50662
50717
  function mainSceneOf(projectFile) {
50663
- const scene = parseProjectGodot(readFileSync36(projectFile, "utf8"))["application"]?.["run/main_scene"];
50718
+ const scene = parseProjectGodot(readFileSync37(projectFile, "utf8"))["application"]?.["run/main_scene"];
50664
50719
  return typeof scene === "string" && scene !== "" ? scene : null;
50665
50720
  }
50666
50721
  function editorPlaysHeadless(projectFile) {
50667
- const runArgs = parseProjectGodot(readFileSync36(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
50722
+ const runArgs = parseProjectGodot(readFileSync37(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
50668
50723
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
50669
50724
  }
50670
50725
  function ownArguments(params) {
@@ -51117,12 +51172,16 @@ class GodotServer {
51117
51172
  };
51118
51173
  const call = this.callsTaken++;
51119
51174
  this.inFlight.set(call, { tool: spec.name, op: checked.op ?? "", startedAt: Date.now() });
51175
+ const slotWaits = { ms: 0, said: undefined };
51120
51176
  try {
51121
51177
  const answer = await withCallSignal(ctx.mcpReq.signal, () => this.answered(spec.name, checked.op ?? "", args), progress, {
51122
51178
  everyMs: CALL_HEARTBEAT_MS,
51123
51179
  said: (elapsedMs) => stillGoingSaid(spec.name, checked.op, elapsedMs)
51180
+ }, slotWaits);
51181
+ const waited = slotWaits.said === undefined || slotWaits.ms < 1000 ? answer : this.saying(answer, {
51182
+ engine_slot_wait: { waitedMs: slotWaits.ms, note: slotWaits.said(slotWaits.ms) }
51124
51183
  });
51125
- return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(answer)));
51184
+ return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(waited)));
51126
51185
  } finally {
51127
51186
  this.inFlight.delete(call);
51128
51187
  }
@@ -51801,6 +51860,10 @@ class GodotServer {
51801
51860
  if (kept !== null) {
51802
51861
  return await this.askTheKeptGame(kept.keeper, keptGameRequest(tool, op, args), kept.projectPath ?? "");
51803
51862
  }
51863
+ const last = this.activeProcess;
51864
+ if (tool === "debug_state" && op === "output" && last !== null && !last.throughEditor && last.keeper !== undefined) {
51865
+ 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"]);
51866
+ }
51804
51867
  if (tool === "debug_state" && op === "output") {
51805
51868
  return await this.handleDAP("dap_get_output", args);
51806
51869
  }
@@ -52579,7 +52642,7 @@ class GodotServer {
52579
52642
  if (!engine.ok) {
52580
52643
  return engine.response;
52581
52644
  }
52582
- const application = parseProjectGodot(readFileSync36(project.value.file, "utf8"))["application"] ?? {};
52645
+ const application = parseProjectGodot(readFileSync37(project.value.file, "utf8"))["application"] ?? {};
52583
52646
  const name = application["config/name"];
52584
52647
  const mainScene = application["run/main_scene"];
52585
52648
  const info = {
@@ -53130,13 +53193,13 @@ ${platform}`;
53130
53193
  const reportsDir = join43(project.value.path, ".godot", ...ours.split("/"));
53131
53194
  let report = null;
53132
53195
  let reportProblem = null;
53133
- const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync36(engineLog, "utf8") : ""), run.log.all);
53196
+ const casePrinted = withNestedErrors(readRun(existsSync34(engineLog) ? readFileSync37(engineLog, "utf8") : ""), run.log.all);
53134
53197
  const declaredErrors = readExpected(expectedFile);
53135
53198
  try {
53136
53199
  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
53200
  const newest = written.at(-1);
53138
53201
  if (newest !== undefined) {
53139
- report = parseJUnit(readFileSync36(join43(reportsDir, newest, "results.xml"), "utf8"));
53202
+ report = parseJUnit(readFileSync37(join43(reportsDir, newest, "results.xml"), "utf8"));
53140
53203
  }
53141
53204
  } catch (error) {
53142
53205
  reportProblem = errorMessage(error);
@@ -53674,7 +53737,7 @@ ${error.text}`;
53674
53737
  return null;
53675
53738
  }
53676
53739
  try {
53677
- return readFileSync36(contained.absolutePath, "utf8");
53740
+ return readFileSync37(contained.absolutePath, "utf8");
53678
53741
  } catch {
53679
53742
  return null;
53680
53743
  }
@@ -53768,7 +53831,7 @@ ${error.text}`;
53768
53831
  return null;
53769
53832
  }
53770
53833
  try {
53771
- return readFileSync36(contained.absolutePath, "utf8");
53834
+ return readFileSync37(contained.absolutePath, "utf8");
53772
53835
  } catch {
53773
53836
  return null;
53774
53837
  }
@@ -54287,7 +54350,7 @@ ${error.text}`;
54287
54350
  return null;
54288
54351
  }
54289
54352
  try {
54290
- return settingKeys(readFileSync36(join43(projectPath, "project.godot"), "utf8"));
54353
+ return settingKeys(readFileSync37(join43(projectPath, "project.godot"), "utf8"));
54291
54354
  } catch {
54292
54355
  return null;
54293
54356
  }
@@ -55112,7 +55175,7 @@ ${error.text}`;
55112
55175
  return 0;
55113
55176
  }
55114
55177
  try {
55115
- return reportAlreadyInTranscript(readFileSync36(game.transcript), readFileSync36(report));
55178
+ return reportAlreadyInTranscript(readFileSync37(game.transcript), readFileSync37(report));
55116
55179
  } catch (error) {
55117
55180
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
55118
55181
  return 0;
@@ -56496,6 +56559,8 @@ ${error.text}`;
56496
56559
  let writtenBefore = null;
56497
56560
  let blind = { unseen: [] };
56498
56561
  let waitedForEditorMs = 0;
56562
+ let answerStalledMs = 0;
56563
+ const deadline = () => started + timeoutMs + answerStalledMs;
56499
56564
  let startUnanswered = false;
56500
56565
  let completedBefore;
56501
56566
  let uidsReread = [];
@@ -56503,7 +56568,15 @@ ${error.text}`;
56503
56568
  for (;; ) {
56504
56569
  const busySince = Date.now();
56505
56570
  let wasBusy = false;
56506
- while (Date.now() - started < timeoutMs && await this.editorIsScanningOrImporting()) {
56571
+ while (Date.now() < deadline()) {
56572
+ const asked = Date.now();
56573
+ if (!await this.editorIsScanningOrImporting()) {
56574
+ const took = Date.now() - asked;
56575
+ if (took >= SLOW_ANSWER_MS) {
56576
+ answerStalledMs += took;
56577
+ }
56578
+ break;
56579
+ }
56507
56580
  wasBusy = true;
56508
56581
  await new Promise((settle) => setTimeout(settle, 100));
56509
56582
  }
@@ -56528,7 +56601,7 @@ ${error.text}`;
56528
56601
  if (duplicated !== undefined) {
56529
56602
  uidsDuplicatedOnDisk = duplicated.map((group) => Array.isArray(group) ? group.map(String) : []);
56530
56603
  }
56531
- if (firstAnswer["started"] !== false || Date.now() - started >= timeoutMs) {
56604
+ if (firstAnswer["started"] !== false || Date.now() >= deadline()) {
56532
56605
  break;
56533
56606
  }
56534
56607
  await new Promise((settle) => setTimeout(settle, 100));
@@ -56538,7 +56611,9 @@ ${error.text}`;
56538
56611
  const ghosts = blind.stillHeld ?? [];
56539
56612
  let busy = true;
56540
56613
  let idleSince = null;
56541
- while (Date.now() - started < timeoutMs) {
56614
+ let looked = false;
56615
+ while (!looked || Date.now() < deadline()) {
56616
+ looked = true;
56542
56617
  await new Promise((settle) => setTimeout(settle, 100));
56543
56618
  let status;
56544
56619
  try {
@@ -56584,6 +56659,7 @@ ${error.text}`;
56584
56659
  let reloaded = {};
56585
56660
  const reloadedFirst = [];
56586
56661
  let builtOn;
56662
+ let builtOnUnchecked = false;
56587
56663
  const reloadingSource = reloading === undefined || projectPath === "" ? null : this.sourceOfClass(projectPath, reloading);
56588
56664
  if (reloading !== undefined && reloadingSource !== null && isToolScript(reloadingSource)) {
56589
56665
  reloaded = {
@@ -56601,6 +56677,7 @@ ${error.text}`;
56601
56677
  answer = await reloadAsked();
56602
56678
  }
56603
56679
  builtOn = first.said;
56680
+ builtOnUnchecked = first.unchecked === true;
56604
56681
  }
56605
56682
  const methods = readArray(answer, "methods");
56606
56683
  const listed = (field) => {
@@ -56627,7 +56704,8 @@ ${error.text}`;
56627
56704
  names: projectPath === "" ? [] : this.classesAScriptNames(projectPath, script),
56628
56705
  restored: answer["restored"] === true,
56629
56706
  restoredAs: readString(answer, "restoredAs") ?? "",
56630
- ...builtOn === undefined ? {} : { builtOn }
56707
+ ...builtOn === undefined ? {} : { builtOn },
56708
+ ...builtOnUnchecked ? { builtOnUnchecked } : {}
56631
56709
  })
56632
56710
  };
56633
56711
  } else {
@@ -56655,6 +56733,9 @@ ${error.text}`;
56655
56733
  const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
56656
56734
  const stillGone = lost.filter((name) => !restored.includes(name));
56657
56735
  const notes = [];
56736
+ if (answerStalledMs > 0) {
56737
+ 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.`);
56738
+ }
56658
56739
  if (busy) {
56659
56740
  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
56741
  if (unseenBefore.length > 0) {
@@ -56683,6 +56764,7 @@ ${error.text}`;
56683
56764
  stillWorking: busy,
56684
56765
  waitedMs: Date.now() - started,
56685
56766
  waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
56767
+ waitedForEditorAnswerMs: answerStalledMs > 0 ? answerStalledMs : undefined,
56686
56768
  reloadedAs: reloaded.as,
56687
56769
  ...reloadDifference(reloaded),
56688
56770
  ...args["listMembers"] === true ? {
@@ -56800,17 +56882,20 @@ ${error.text}`;
56800
56882
  try {
56801
56883
  const answer = asParams(await this.godotBridge.invokeTool("scripts_behind", { scriptPaths }));
56802
56884
  const behind = readArray(answer, "behind");
56803
- return behind === undefined ? null : { behind: behind.map(String), unsaved: (readArray(answer, "unsavedInEditor") ?? []).map(String) };
56885
+ return behind === undefined ? { unchecked: "the editor answered without a list of them" } : { behind: behind.map(String), unsaved: (readArray(answer, "unsavedInEditor") ?? []).map(String) };
56804
56886
  } catch (error) {
56805
- this.logDebug(`The editor would not say which scripts it holds behind their files: ${errorMessage(error)}`);
56806
- return null;
56887
+ return { unchecked: `the editor would not say: ${errorMessage(error)}` };
56807
56888
  }
56808
56889
  }
56809
56890
  async reloadWhatItIsBuiltOn(projectPath, scriptPath) {
56810
56891
  const against = this.scriptsCompiledAgainst(projectPath, scriptPath);
56811
56892
  const held = await this.scriptsHeldBehind(against);
56812
- if (held === null) {
56813
- return { reloaded: [] };
56893
+ if ("unchecked" in held) {
56894
+ return {
56895
+ reloaded: [],
56896
+ said: `Whether the editor holds the scripts it is compiled against behind their files could not be checked: ${held.unchecked}.`,
56897
+ unchecked: true
56898
+ };
56814
56899
  }
56815
56900
  const reloadedFor = scriptPath.startsWith("res://") ? scriptPath : `res://${scriptPath.replace(/^\/+/, "")}`;
56816
56901
  const reloaded = [];
@@ -57460,10 +57545,11 @@ ${error.text}`;
57460
57545
  const checked = await this.classesTheEditorCannotSee(args);
57461
57546
  const stillHeld = checked.stillHeld ?? [];
57462
57547
  const projectPath = typeof args["projectPath"] === "string" ? args["projectPath"] : "";
57463
- const listed = projectPath === "" ? null : cachedClasses(projectPath);
57548
+ const listed = projectPath === "" || checked.unchecked !== undefined ? null : cachedClasses(projectPath);
57464
57549
  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) {
57550
+ const behindUnchecked = held !== null && "unchecked" in held ? held.unchecked : undefined;
57551
+ const behind = listed === null || held === null || "unchecked" in held ? [] : [...listed].filter(([, path]) => held.behind.includes(path)).map(([className, scriptPath]) => ({ className, scriptPath }));
57552
+ if (checked.unseen.length === 0 && stillHeld.length === 0 && checked.unchecked === undefined && behindUnchecked === undefined && behind.length === 0) {
57467
57553
  return answered;
57468
57554
  }
57469
57555
  const payload = asParams(JSON.parse(first.text));
@@ -57486,6 +57572,7 @@ ${error.text}`;
57486
57572
  stillHeldByEditor: stillHeld.length > 0 ? stillHeld : undefined,
57487
57573
  heldBehindInEditor: behind.length > 0 ? behind : undefined,
57488
57574
  classesUnchecked: checked.unchecked,
57575
+ heldBehindUnchecked: behindUnchecked,
57489
57576
  note: notes.length > 0 ? notes.join(" ") : undefined
57490
57577
  });
57491
57578
  }
@@ -57495,7 +57582,7 @@ ${error.text}`;
57495
57582
  }
57496
57583
  const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57497
57584
  const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
57498
- if (declared.test(readFileSync36(scriptPath, "utf8"))) {
57585
+ if (declared.test(readFileSync37(scriptPath, "utf8"))) {
57499
57586
  return null;
57500
57587
  }
57501
57588
  const checked = await this.classesTheEditorCannotSee(args);
@@ -58006,7 +58093,7 @@ async function runGodotServer() {
58006
58093
  const server = new GodotServer;
58007
58094
  await server.run();
58008
58095
  }
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;
58096
+ 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
58097
  var init_server = __esm(() => {
58011
58098
  init_dist2();
58012
58099
  init_stdio();
@@ -58179,7 +58266,7 @@ init_alive();
58179
58266
  init_engine_launch();
58180
58267
  init_lsp_client();
58181
58268
  init_process_children();
58182
- import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync15, unlinkSync as unlinkSync2 } from "node:fs";
58269
+ import { existsSync as existsSync17, readdirSync as readdirSync10, readFileSync as readFileSync16, unlinkSync as unlinkSync2 } from "node:fs";
58183
58270
  import { connect as connect2 } from "node:net";
58184
58271
  import { join as join20, resolve as resolve7 } from "node:path";
58185
58272
  var EDITOR_RECORD_PROTOCOL = 1;
@@ -58198,7 +58285,7 @@ function editorRecordsIn(projectPath) {
58198
58285
  for (const name of names) {
58199
58286
  const file = join20(directory, name);
58200
58287
  try {
58201
- const said = JSON.parse(readFileSync15(file, "utf8"));
58288
+ const said = JSON.parse(readFileSync16(file, "utf8"));
58202
58289
  const { protocol, pid, lsp_port: lspPort, written_at_ms: writtenAtMs } = said;
58203
58290
  if (protocol === EDITOR_RECORD_PROTOCOL && typeof pid === "number" && typeof lspPort === "number" && lspPort > 0 && typeof writtenAtMs === "number") {
58204
58291
  records.push({ file, pid, lspPort, writtenAtMs });
@@ -58707,7 +58794,7 @@ init_engine_launch();
58707
58794
  init_engine_slots();
58708
58795
  init_setup();
58709
58796
  init_suite_judge();
58710
- import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync18, rmSync as rmSync11 } from "node:fs";
58797
+ import { existsSync as existsSync20, mkdirSync as mkdirSync8, readFileSync as readFileSync19, rmSync as rmSync11 } from "node:fs";
58711
58798
  import { availableParallelism as availableParallelism3 } from "node:os";
58712
58799
  import { join as join23, resolve as resolve9 } from "node:path";
58713
58800
  import process13 from "node:process";
@@ -58895,17 +58982,17 @@ async function runSpreadCommand(options, godotPath) {
58895
58982
  const faults = left.flatMap((worker) => engineFaults(worker, options.godotVersion));
58896
58983
  const arms = left.map((_, index) => {
58897
58984
  const file = join23(shareDirectory, `worker-${String(index)}.arms`);
58898
- return existsSync20(file) ? armsPlayed(readFileSync18(file, "utf8")) : [];
58985
+ return existsSync20(file) ? armsPlayed(readFileSync19(file, "utf8")) : [];
58899
58986
  });
58900
58987
  faults.push(...shareFaults(arms));
58901
58988
  if (faults.length > 0) {
58902
58989
  return failed(faults, left, runDirectory, "so nothing was gathered");
58903
58990
  }
58904
58991
  const gather = await engine("gather", `${GATHER}${String(workers)}`);
58905
- console.log(readFileSync18(gather.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd());
58992
+ console.log(readFileSync19(gather.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd());
58906
58993
  const gatherFaults = engineFaults(gather, options.godotVersion);
58907
58994
  const gatheredFile = join23(shareDirectory, "gathered.arms");
58908
- const gathered = new Set((existsSync20(gatheredFile) ? armsPlayed(readFileSync18(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
58995
+ const gathered = new Set((existsSync20(gatheredFile) ? armsPlayed(readFileSync19(gatheredFile, "utf8")) ?? [] : []).map((arm) => arm.arm));
58909
58996
  const ungathered = (arms[0] ?? []).filter((arm) => !gathered.has(arm.arm)).map((arm) => arm.arm);
58910
58997
  if (ungathered.length > 0) {
58911
58998
  gatherFaults.push(`the gathering run never asked for ${ungathered.join(", ")}, which the workers played: it went another way through the arms than they did`);
@@ -58959,7 +59046,7 @@ function engineFaults(engine, godotVersion) {
58959
59046
  if (!existsSync20(engine.log)) {
58960
59047
  return faults;
58961
59048
  }
58962
- const printed = readRun(readFileSync18(engine.log, "utf8"));
59049
+ const printed = readRun(readFileSync19(engine.log, "utf8"));
58963
59050
  const wrongEngine = godotVersion === null ? null : versionFault(printed.engineVersion, godotVersion);
58964
59051
  if (wrongEngine !== null) {
58965
59052
  faults.push(`${engine.name}: ${wrongEngine}`);
@@ -58972,7 +59059,7 @@ function engineFaults(engine, godotVersion) {
58972
59059
  }
58973
59060
  function progressOf(logs) {
58974
59061
  const each = logs.map((log, index) => {
58975
- const text = existsSync20(log) ? readFileSync18(log, "utf8") : "";
59062
+ const text = existsSync20(log) ? readFileSync19(log, "utf8") : "";
58976
59063
  const last = [...text.matchAll(PROGRESS)].at(-1);
58977
59064
  return last === undefined ? `worker ${String(index)} has played no arm yet` : `worker ${String(index)} at ${last[1] ?? ""} ${last[2] ?? ""} of ${last[3] ?? ""}`;
58978
59065
  });
@@ -59004,7 +59091,7 @@ import {
59004
59091
  existsSync as existsSync23,
59005
59092
  mkdirSync as mkdirSync10,
59006
59093
  readdirSync as readdirSync14,
59007
- readFileSync as readFileSync22,
59094
+ readFileSync as readFileSync23,
59008
59095
  rmSync as rmSync12,
59009
59096
  statSync as statSync14,
59010
59097
  writeFileSync as writeFileSync12
@@ -59016,7 +59103,7 @@ import process14 from "node:process";
59016
59103
  // src/suite-fingerprint.ts
59017
59104
  init_class_cache();
59018
59105
  import { createHash as createHash6 } from "node:crypto";
59019
- import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync19, statSync as statSync13 } from "node:fs";
59106
+ import { existsSync as existsSync21, readdirSync as readdirSync13, readFileSync as readFileSync20, statSync as statSync13 } from "node:fs";
59020
59107
  import { join as join24, posix as posix2 } from "node:path";
59021
59108
  var RES_LITERAL = /res:\/\/[^"'\s)\]<>]*/g;
59022
59109
  var RELATIVE_LOAD = /\b(?:pre)?load\(\s*["']((?!res:\/\/|uid:\/\/)[^"']+)["']/g;
@@ -59081,7 +59168,7 @@ ${this.read(path).digest}
59081
59168
  walk(file, `${under}${entry.name}/`);
59082
59169
  } else if (entry.isFile()) {
59083
59170
  hash.update(`${under}${entry.name}
59084
- `).update(readFileSync19(file));
59171
+ `).update(readFileSync20(file));
59085
59172
  }
59086
59173
  }
59087
59174
  };
@@ -59107,7 +59194,7 @@ ${this.read(path).digest}
59107
59194
  if (BINARY_RESOURCE.test(path)) {
59108
59195
  return { digest: "", reaches: [], unreadable: `${path} is a binary resource` };
59109
59196
  }
59110
- const bytes = readFileSync19(file);
59197
+ const bytes = readFileSync20(file);
59111
59198
  const digest = createHash6("sha256").update(bytes).digest("hex");
59112
59199
  const text = bytes.toString("latin1");
59113
59200
  const reaches = new Set;
@@ -59145,7 +59232,7 @@ ${this.read(path).digest}
59145
59232
  }
59146
59233
  function extensionsOf(projectPath) {
59147
59234
  const list = join24(projectPath, ".godot", "extension_list.cfg");
59148
- return existsSync21(list) ? readFileSync19(list, "utf8").split(`
59235
+ return existsSync21(list) ? readFileSync20(list, "utf8").split(`
59149
59236
  `).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
59150
59237
  }
59151
59238
 
@@ -59154,7 +59241,7 @@ init_suite_judge();
59154
59241
  init_suite_plan();
59155
59242
 
59156
59243
  // src/version.ts
59157
- import { readFileSync as readFileSync21 } from "node:fs";
59244
+ import { readFileSync as readFileSync22 } from "node:fs";
59158
59245
  import { dirname as dirname12, join as join26 } from "node:path";
59159
59246
  import { fileURLToPath as fileURLToPath3 } from "node:url";
59160
59247
  function getLocalVersion() {
@@ -59165,7 +59252,7 @@ function getLocalVersion() {
59165
59252
  ];
59166
59253
  for (const packagePath of packageCandidates) {
59167
59254
  try {
59168
- const value = JSON.parse(readFileSync21(packagePath, "utf-8"));
59255
+ const value = JSON.parse(readFileSync22(packagePath, "utf-8"));
59169
59256
  if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
59170
59257
  return value.version;
59171
59258
  }
@@ -59426,9 +59513,9 @@ async function runTestCommand(options, godotPath) {
59426
59513
  }
59427
59514
  const fingerprints = new SuiteFingerprints(scripts, [
59428
59515
  getLocalVersion(),
59429
- readFileSync22(shippedListedRunner(), "utf8"),
59516
+ readFileSync23(shippedListedRunner(), "utf8"),
59430
59517
  ...options.reachAlso.map((path) => `${path}
59431
- ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
59518
+ ${existsSync23(path) ? readFileSync23(path, "latin1") : "missing"}`)
59432
59519
  ]);
59433
59520
  const printsOf = new Map(suites.map((suite) => [suite.path, fingerprints.of(suite)]));
59434
59521
  if (options.changedOnly) {
@@ -59544,7 +59631,7 @@ function roomSaid(engines, lanes, unheld, held, asked) {
59544
59631
  }
59545
59632
  function readRecord(file) {
59546
59633
  try {
59547
- const parsed = JSON.parse(readFileSync22(file, "utf8"));
59634
+ const parsed = JSON.parse(readFileSync23(file, "utf8"));
59548
59635
  return typeof parsed === "object" && parsed !== null ? parsed : {};
59549
59636
  } catch {
59550
59637
  return {};
@@ -59603,7 +59690,7 @@ async function importProject(launch) {
59603
59690
  if (ended.ended !== null || ended.code !== 0) {
59604
59691
  return `${ended.ended ?? `the engine exited ${exitNamed(ended.code ?? 0)}`}; everything it printed is in ${launch.logs.named(log)}`;
59605
59692
  }
59606
- const printed = readRun(readFileSync22(log, "utf8"));
59693
+ const printed = readRun(readFileSync23(log, "utf8"));
59607
59694
  const wrongEngine = launch.godotVersion === null ? null : versionFault(printed.engineVersion, launch.godotVersion);
59608
59695
  if (wrongEngine !== null) {
59609
59696
  return `${wrongEngine}; everything it printed is in ${launch.logs.named(log)}`;
@@ -59719,7 +59806,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59719
59806
  launch.slots.release(lock);
59720
59807
  }
59721
59808
  }
59722
- const printed = readRun(readFileSync22(log, "utf8"));
59809
+ const printed = readRun(readFileSync23(log, "utf8"));
59723
59810
  const assignment = {
59724
59811
  suites: plan.suites,
59725
59812
  expected: readExpected(expectedFile),
@@ -59754,7 +59841,7 @@ function report(ran, across, uncollected, startedAt, logs, printOutput, targets)
59754
59841
  for (const engine of ran) {
59755
59842
  console.log("");
59756
59843
  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)");
59844
+ console.log(existsSync23(engine.log) ? readFileSync23(engine.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd() : "(no log was written)");
59758
59845
  }
59759
59846
  }
59760
59847
  const failed = ran.filter((engine) => !engine.verdict.passed);
@@ -59773,7 +59860,7 @@ function report(ran, across, uncollected, startedAt, logs, printOutput, targets)
59773
59860
  writeFileSync12(engine.log, `${verdict.join(`
59774
59861
  `)}
59775
59862
  ---
59776
- ${readFileSync22(engine.log, "utf8")}`);
59863
+ ${readFileSync23(engine.log, "utf8")}`);
59777
59864
  }
59778
59865
  if (engine.verdict.shown.length > 0) {
59779
59866
  console.log("");