gdharness 1.3.42 → 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.
Files changed (106) hide show
  1. package/build/cli.js +383 -243
  2. package/build/godot/addons/auto_reload/auto_reload.gd +18 -19
  3. package/build/godot/addons/gdharness_editor/animation_reads.gd +55 -33
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +53 -58
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +68 -32
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +83 -42
  7. package/build/godot/addons/gdharness_editor/file_walk.gd +12 -5
  8. package/build/godot/addons/gdharness_editor/headless_check.gd +68 -44
  9. package/build/godot/addons/gdharness_editor/plugin.gd +2 -3
  10. package/build/godot/addons/gdharness_editor/property_paths.gd +22 -12
  11. package/build/godot/addons/gdharness_editor/property_values.gd +143 -78
  12. package/build/godot/addons/gdharness_editor/reading.gd +51 -17
  13. package/build/godot/addons/gdharness_editor/same_values.gd +4 -4
  14. package/build/godot/addons/gdharness_editor/scene_file.gd +127 -80
  15. package/build/godot/addons/gdharness_editor/scene_uids.gd +12 -6
  16. package/build/godot/addons/gdharness_editor/serialisation.gd +75 -51
  17. package/build/godot/addons/gdharness_editor/setting_reads.gd +7 -6
  18. package/build/godot/addons/gdharness_editor/tool_executor.gd +3 -6
  19. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +37 -27
  20. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +79 -49
  21. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +70 -40
  22. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +65 -42
  23. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +24 -22
  24. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +49 -29
  25. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +26 -21
  26. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +40 -40
  27. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +166 -107
  28. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +16 -9
  29. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +68 -41
  30. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +35 -20
  31. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +28 -13
  32. package/build/godot/addons/gdharness_editor/tools/scene_sharing.gd +12 -5
  33. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +113 -67
  34. package/build/godot/addons/gdharness_editor/tools/script_paths.gd +20 -8
  35. package/build/godot/addons/gdharness_editor/tools/settings_tools.gd +11 -8
  36. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +47 -34
  37. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +77 -50
  38. package/build/godot/addons/gdharness_editor/value_shapes.gd +5 -6
  39. package/build/godot/addons/gdharness_runtime/call_errors.gd +1 -2
  40. package/build/godot/addons/gdharness_runtime/declared_values.gd +68 -32
  41. package/build/godot/addons/gdharness_runtime/error_report.gd +1 -2
  42. package/build/godot/addons/gdharness_runtime/reading.gd +51 -17
  43. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +84 -63
  44. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +149 -147
  45. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +64 -42
  46. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +184 -122
  47. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +2 -2
  48. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +24 -20
  49. package/build/godot/addons/gdharness_runtime/runtime_input.gd +134 -92
  50. package/build/godot/addons/gdharness_runtime/runtime_items.gd +29 -13
  51. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +0 -1
  52. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +34 -17
  53. package/build/godot/addons/gdharness_runtime/runtime_pad.gd +17 -13
  54. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +84 -46
  55. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +117 -79
  56. package/build/godot/addons/gdharness_runtime/runtime_says.gd +14 -17
  57. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +23 -16
  58. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +117 -66
  59. package/build/godot/addons/gdharness_runtime/runtime_touch.gd +15 -15
  60. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +44 -27
  61. package/build/godot/addons/gdharness_runtime/runtime_values.gd +108 -73
  62. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +111 -84
  63. package/build/godot/addons/gdharness_runtime/runtime_words.gd +10 -7
  64. package/build/godot/addons/gdharness_runtime/serialisation.gd +75 -51
  65. package/build/godot/addons/gdharness_runtime/value_shapes.gd +5 -6
  66. package/build/godot/addons/gdharness_tests/bench.gd +4 -5
  67. package/build/godot/addons/gdharness_tests/event.gd +10 -5
  68. package/build/godot/addons/gdharness_tests/share.gd +38 -18
  69. package/build/godot/addons/gdharness_tests/suites.gd +3 -3
  70. package/build/godot/addons/gdharness_tests/wait.gd +3 -2
  71. package/build/godot/loader/loader.gd +9 -9
  72. package/build/godot/operations/animation_reads.gd +55 -33
  73. package/build/godot/operations/audio_buses.gd +48 -19
  74. package/build/godot/operations/class_cache.gd +16 -11
  75. package/build/godot/operations/classdb_queries.gd +89 -44
  76. package/build/godot/operations/declared_values.gd +68 -32
  77. package/build/godot/operations/dependencies.gd +190 -109
  78. package/build/godot/operations/editor_tools.gd +10 -10
  79. package/build/godot/operations/export_presets.gd +100 -54
  80. package/build/godot/operations/file_reads.gd +83 -42
  81. package/build/godot/operations/file_walk.gd +12 -5
  82. package/build/godot/operations/gdscript_analysis.gd +86 -65
  83. package/build/godot/operations/gdscript_authoring.gd +83 -36
  84. package/build/godot/operations/godot_operations.gd +17 -11
  85. package/build/godot/operations/import_pipeline.gd +110 -59
  86. package/build/godot/operations/input_actions.gd +62 -39
  87. package/build/godot/operations/patterns.gd +3 -1
  88. package/build/godot/operations/plugins.gd +36 -16
  89. package/build/godot/operations/project_config.gd +61 -32
  90. package/build/godot/operations/project_diagnostics.gd +24 -11
  91. package/build/godot/operations/project_file.gd +16 -16
  92. package/build/godot/operations/project_validation.gd +71 -44
  93. package/build/godot/operations/property_paths.gd +22 -12
  94. package/build/godot/operations/property_values.gd +143 -78
  95. package/build/godot/operations/reading.gd +51 -17
  96. package/build/godot/operations/rename_checks.gd +2 -2
  97. package/build/godot/operations/resource_files.gd +11 -9
  98. package/build/godot/operations/same_values.gd +4 -4
  99. package/build/godot/operations/scene_connections.gd +27 -13
  100. package/build/godot/operations/script_check.gd +52 -49
  101. package/build/godot/operations/serialisation.gd +75 -51
  102. package/build/godot/operations/setting_reads.gd +7 -6
  103. package/build/godot/operations/value_shapes.gd +5 -6
  104. package/build/godot/tests/listed.gd +321 -268
  105. package/build/keeper.js +43 -43
  106. package/package.json +1 -1
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;
@@ -10914,6 +10978,36 @@ function rowOf(name) {
10914
10978
  function rowRuns(share, row) {
10915
10979
  return "only" in share ? row !== null && share.only.includes(row) : row === null || !share.besides.includes(row);
10916
10980
  }
10981
+ function mismatchedRowsIn(printed) {
10982
+ const lines = printed.flatMap((text) => stripVTControlCharacters(text).split(/\r?\n/));
10983
+ const found = [];
10984
+ let suite = null;
10985
+ let lastCase = null;
10986
+ for (let at = 0;at < lines.length; at += 1) {
10987
+ const line = lines[at] ?? "";
10988
+ const opened = SUITE_OPENED.exec(line)?.[1];
10989
+ if (opened !== undefined) {
10990
+ suite = opened;
10991
+ lastCase = null;
10992
+ continue;
10993
+ }
10994
+ const caseLine = CASE_LINE.exec(line);
10995
+ if (caseLine !== null) {
10996
+ lastCase = caseLine[3] === "STARTED" ? lastCase : caseLine[2] ?? null;
10997
+ continue;
10998
+ }
10999
+ if (suite === null || lastCase === null || !MISMATCHED_ROW.test(line)) {
11000
+ continue;
11001
+ }
11002
+ const parts = [line.trim()];
11003
+ while (at + 1 < lines.length && !["", "'"].includes((lines[at + 1] ?? "").trim())) {
11004
+ at += 1;
11005
+ parts.push((lines[at] ?? "").trim());
11006
+ }
11007
+ found.push({ suite, name: lastCase, reason: parts.join(" ").replaceAll(/\[\/?color[^\]]*\]/g, "") });
11008
+ }
11009
+ return found;
11010
+ }
10917
11011
  function readRun(printed) {
10918
11012
  const lines = stripVTControlCharacters(printed).replaceAll(`\r
10919
11013
  `, `
@@ -11045,7 +11139,8 @@ function readRun(printed) {
11045
11139
  running: last === null ? null : { suite: last.path, during },
11046
11140
  leftOut,
11047
11141
  engineVersion,
11048
- orphansLeft
11142
+ orphansLeft,
11143
+ mismatched: mismatchedRowsIn(lines)
11049
11144
  };
11050
11145
  }
11051
11146
  function withNestedErrors(printed, piped) {
@@ -11064,7 +11159,7 @@ function versionFault(said, wanted) {
11064
11159
  function readExpected(file) {
11065
11160
  const declared = (value) => typeof value === "object" && value !== null ? value : {};
11066
11161
  try {
11067
- const parsed = JSON.parse(readFileSync6(file, "utf8"));
11162
+ const parsed = JSON.parse(readFileSync7(file, "utf8"));
11068
11163
  const read = typeof parsed === "object" && parsed !== null ? parsed : {};
11069
11164
  return { errors: declared(read["errors"]), warnings: declared(read["warnings"]) };
11070
11165
  } catch {
@@ -11170,6 +11265,10 @@ function judgeEngine(printed, handed, end) {
11170
11265
  if (printed.executed === null || printed.executed === 0) {
11171
11266
  faults.push(printed.executed === null ? "the run never printed its summary" : "the run executed no test case at all, whatever it exited with");
11172
11267
  }
11268
+ for (const row of printed.mismatched) {
11269
+ faults.push(`${row.suite} > ${row.name} never ran: gdUnit4 skipped it, saying "${row.reason}"`);
11270
+ failing.add(row.suite);
11271
+ }
11173
11272
  for (const { path, cases, rows = {} } of handed.suites) {
11174
11273
  const suite = printed.suites.get(path);
11175
11274
  if (suite === undefined) {
@@ -11333,7 +11432,7 @@ ${String(error.warning)}`);
11333
11432
  const neverPrinted = [...owed.values()].flatMap((debt) => debt.texts.map((text) => ({ suite: debt.suite, during: debt.during, text, warning: debt.warning })));
11334
11433
  return { printed: unexpected, neverPrinted };
11335
11434
  }
11336
- var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, BANNER, LEFT_OUT, DRIVER_FALLBACK, MACHINE_WARNED, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_ORPHANS = 101, GDUNIT_EXITS, GODOT_EXITS, TAIL = 60, MOST_NAMED = 8;
11435
+ var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, WARNING_LINE, EXECUTED, MISMATCHED_ROW, BANNER, LEFT_OUT, DRIVER_FALLBACK, MACHINE_WARNED, ORPHANS_REPORTED, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_ORPHANS = 101, GDUNIT_EXITS, GODOT_EXITS, TAIL = 60, MOST_NAMED = 8;
11337
11436
  var init_suite_judge = __esm(() => {
11338
11437
  init_engine_leaks();
11339
11438
  init_junit();
@@ -11344,6 +11443,7 @@ var init_suite_judge = __esm(() => {
11344
11443
  ERROR_LINE = /^\s*(?:SCRIPT |USER )?ERROR: (.*?)\s*$/;
11345
11444
  WARNING_LINE = /^\s*(?:SCRIPT |USER )?WARNING: (.*?)\s*$/;
11346
11445
  EXECUTED = /^Executed test cases\s*:\s*\((\d+)\//;
11446
+ MISMATCHED_ROW = /^\s*The test data set at index \(\d+\) does not match the expected test parameters/;
11347
11447
  BANNER = /^\s*Godot Engine v(\S+?)(?:\s+-\s+\S+)?\s*$/;
11348
11448
  LEFT_OUT = /^gdharness left out (res:\/\/.+?\.gd) > ([^:\s]+):(-?\d+)\s*$/;
11349
11449
  DRIVER_FALLBACK = /^\s*WARNING: Your video card drivers .*, switching to /;
@@ -11381,9 +11481,9 @@ import {
11381
11481
  openSync as openSync2,
11382
11482
  readdirSync as readdirSync3,
11383
11483
  readSync,
11384
- rmSync as rmSync4,
11484
+ rmSync as rmSync5,
11385
11485
  statSync as statSync5,
11386
- writeFileSync as writeFileSync4,
11486
+ writeFileSync as writeFileSync5,
11387
11487
  writeSync as writeSync2
11388
11488
  } from "node:fs";
11389
11489
  import { availableParallelism, freemem, totalmem } from "node:os";
@@ -11459,14 +11559,14 @@ function sweepRunDirectories(runs, kept = KEPT_RUN_DIRECTORIES) {
11459
11559
  const named = /^(\d+)-(\d+)$/.exec(entry);
11460
11560
  const pid = Number(named?.[2]);
11461
11561
  if (!Number.isInteger(pid)) {
11462
- rmSync4(join7(runs, entry), { recursive: true, force: true });
11562
+ rmSync5(join7(runs, entry), { recursive: true, force: true });
11463
11563
  } else if (!alive(pid)) {
11464
11564
  ended.push({ entry, started: Number(named?.[1]) });
11465
11565
  }
11466
11566
  }
11467
11567
  ended.sort((a, b) => b.started - a.started);
11468
11568
  for (const { entry } of ended.slice(kept)) {
11469
- rmSync4(join7(runs, entry), { recursive: true, force: true });
11569
+ rmSync5(join7(runs, entry), { recursive: true, force: true });
11470
11570
  }
11471
11571
  }
11472
11572
  function roomFor(most, processors, available, said = "of memory free", processorsSaid = `the machine has ${plural(processors, "processor")}`) {
@@ -11571,7 +11671,7 @@ async function watchedEngine(launch, args, directory, log, silenceSeconds, silen
11571
11671
  piped = [helped.stdout, helped.stderr];
11572
11672
  child = helped;
11573
11673
  stop = () => {
11574
- writeFileSync4(stopFile, "");
11674
+ writeFileSync5(stopFile, "");
11575
11675
  };
11576
11676
  } else {
11577
11677
  const held = heldToCores(launch.cores, launch.godotPath, args);
@@ -11725,9 +11825,9 @@ function stillGoingSaid(tool, op, elapsedMs) {
11725
11825
  const took = seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
11726
11826
  return `${op === null || op === "" ? tool : `${tool} ${op}`} is still going; ${took} in.`;
11727
11827
  }
11728
- async function withCallSignal(signal, answer, progress, still) {
11828
+ async function withCallSignal(signal, answer, progress, still, slotWaits) {
11729
11829
  if (progress === undefined) {
11730
- return signal === undefined ? answer() : current.run({ signal, progress: undefined }, answer);
11830
+ return signal === undefined && slotWaits === undefined ? answer() : current.run({ signal, progress: undefined, slotWaits }, answer);
11731
11831
  }
11732
11832
  const began = Date.now();
11733
11833
  const rising = risingOnly(progress);
@@ -11751,7 +11851,7 @@ async function withCallSignal(signal, answer, progress, still) {
11751
11851
  };
11752
11852
  rearm();
11753
11853
  try {
11754
- return await current.run({ signal, progress: told }, answer);
11854
+ return await current.run({ signal, progress: told, slotWaits }, answer);
11755
11855
  } finally {
11756
11856
  going = undefined;
11757
11857
  clearTimeout(quiet);
@@ -11770,63 +11870,20 @@ function callSignal() {
11770
11870
  function callProgress() {
11771
11871
  return current.getStore()?.progress;
11772
11872
  }
11873
+ function callSlotWaits() {
11874
+ return current.getStore()?.slotWaits;
11875
+ }
11773
11876
  var current, CALL_HEARTBEAT_MS = 20000;
11774
11877
  var init_call_signal = __esm(() => {
11775
11878
  current = new AsyncLocalStorage;
11776
11879
  });
11777
11880
 
11778
- // src/whole-file.ts
11779
- import { renameSync, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
11780
- function writeWhole(path, text) {
11781
- const partial = `${path}.${process.pid}.partial`;
11782
- try {
11783
- writeFileSync5(partial, text, "utf8");
11784
- } catch {
11785
- return false;
11786
- }
11787
- const until = Date.now() + HELD_FILE_MS;
11788
- for (;; ) {
11789
- try {
11790
- renameSync(partial, path);
11791
- return true;
11792
- } catch (error) {
11793
- const code = error.code ?? "";
11794
- if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
11795
- try {
11796
- rmSync5(partial, { force: true });
11797
- } catch {}
11798
- return false;
11799
- }
11800
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
11801
- }
11802
- }
11803
- }
11804
- function removeWhole(path) {
11805
- const until = Date.now() + HELD_FILE_MS;
11806
- for (;; ) {
11807
- try {
11808
- rmSync5(path, { force: true });
11809
- return true;
11810
- } catch (error) {
11811
- const code = error.code ?? "";
11812
- if (!HELD_BY_A_READER.has(code) || Date.now() >= until) {
11813
- return false;
11814
- }
11815
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, HELD_FILE_RETRY_MS);
11816
- }
11817
- }
11818
- }
11819
- var HELD_FILE_MS = 1000, HELD_FILE_RETRY_MS = 10, HELD_BY_A_READER;
11820
- var init_whole_file = __esm(() => {
11821
- HELD_BY_A_READER = new Set(["EPERM", "EACCES", "EBUSY"]);
11822
- });
11823
-
11824
11881
  // src/engine-slots.ts
11825
11882
  import {
11826
11883
  closeSync as closeSync3,
11827
11884
  mkdirSync as mkdirSync3,
11828
11885
  openSync as openSync3,
11829
- readFileSync as readFileSync7,
11886
+ readFileSync as readFileSync8,
11830
11887
  rmSync as rmSync6,
11831
11888
  statSync as statSync6,
11832
11889
  watch,
@@ -11956,7 +12013,7 @@ class EngineSlots {
11956
12013
  this.holder = holder;
11957
12014
  this.say = say;
11958
12015
  }
11959
- async take(signal, waiting) {
12016
+ async take(signal, waiting, blocked) {
11960
12017
  mkdirSync3(this.directory, { recursive: true });
11961
12018
  const began = Date.now();
11962
12019
  let said = Number.NEGATIVE_INFINITY;
@@ -11969,7 +12026,10 @@ class EngineSlots {
11969
12026
  if (lock !== null) {
11970
12027
  return lock;
11971
12028
  }
11972
- mark ??= this.waitMarked();
12029
+ if (mark === undefined) {
12030
+ mark = this.waitMarked();
12031
+ blocked?.();
12032
+ }
11973
12033
  const now = Date.now();
11974
12034
  if (waiting !== undefined) {
11975
12035
  if (now - said >= WAIT_SAID_MS) {
@@ -12023,7 +12083,7 @@ class EngineSlots {
12023
12083
  const found = [];
12024
12084
  for (let slot = 0;slot < this.count; slot += 1) {
12025
12085
  try {
12026
- const holder = holderOf(readFileSync7(join8(this.directory, `${String(slot)}.lock`), "utf8"));
12086
+ const holder = holderOf(readFileSync8(join8(this.directory, `${String(slot)}.lock`), "utf8"));
12027
12087
  if (holder !== null) {
12028
12088
  found.push(holder);
12029
12089
  }
@@ -12043,7 +12103,7 @@ class EngineSlots {
12043
12103
  release(lock) {
12044
12104
  this.held.delete(lock);
12045
12105
  try {
12046
- if (holderOf(readFileSync7(lock, "utf8"))?.pid === this.holder.pid) {
12106
+ if (holderOf(readFileSync8(lock, "utf8"))?.pid === this.holder.pid) {
12047
12107
  removeWhole(lock);
12048
12108
  }
12049
12109
  } catch {}
@@ -12093,7 +12153,7 @@ class EngineSlots {
12093
12153
  let content;
12094
12154
  let writtenAt;
12095
12155
  try {
12096
- content = readFileSync7(lock, "utf8");
12156
+ content = readFileSync8(lock, "utf8");
12097
12157
  writtenAt = statSync6(lock).mtimeMs;
12098
12158
  } catch {
12099
12159
  return false;
@@ -12131,31 +12191,55 @@ function nameSlotTaker(by) {
12131
12191
  }
12132
12192
  function slotWaitSaid(waitedMs, count, directory, holders = []) {
12133
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) {
12134
12214
  const held = new Map;
12135
12215
  for (const holder of holders) {
12136
12216
  const known = held.get(holder.pid);
12137
12217
  held.set(holder.pid, { by: known?.by ?? holder.by, slots: (known?.slots ?? 0) + 1 });
12138
12218
  }
12139
- const named = [...held].map(([pid, { by, slots: taken }]) => {
12219
+ return [...held].map(([pid, { by, slots: taken }]) => {
12140
12220
  const who = pid === process7.pid ? `this process (pid ${String(pid)})` : by === undefined ? `pid ${String(pid)}` : `${by} (pid ${String(pid)})`;
12141
12221
  return count === 1 ? who : `${String(taken)} by ${who}`;
12142
12222
  });
12143
- const waiting = `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine: ${slots}`;
12144
- if (named.length === 0) {
12145
- return `${waiting}, by a gate or another tool, and the engine starts when one is given back.`;
12146
- }
12147
- if (count === 1) {
12148
- return `${waiting}, by ${named.join(", ")}, and the engine starts when it is given back.`;
12149
- }
12150
- const list = named.length === 1 ? named.join("") : `${named.slice(0, -1).join(", ")} and ${named.at(-1) ?? ""}`;
12151
- 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) ?? ""}`;
12152
12226
  }
12153
12227
  async function slotForCall() {
12154
12228
  const slots = await processSlots();
12155
12229
  const progress = callProgress();
12230
+ const waits = callSlotWaits();
12231
+ const began = Date.now();
12232
+ let blockedBy;
12156
12233
  const lock = await slots.take(callSignal(), progress === undefined ? undefined : (waitedMs) => {
12157
12234
  progress(waitedMs / 1000, slotWaitSaid(waitedMs, slots.count, slots.directory, slots.holders()));
12235
+ }, () => {
12236
+ blockedBy = slots.holders();
12158
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
+ }
12159
12243
  return {
12160
12244
  release: () => {
12161
12245
  slots.release(lock);
@@ -12925,7 +13009,7 @@ var init_doc_cache = __esm(() => {
12925
13009
  });
12926
13010
 
12927
13011
  // src/project-scan.ts
12928
- 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";
12929
13013
  import { basename as basename5, extname, join as join11, relative as relative4, sep as sep3 } from "node:path";
12930
13014
  function skipped(name) {
12931
13015
  return name.startsWith(".") || SKIPPED.has(name);
@@ -13061,7 +13145,7 @@ function scriptsWithoutUid(projectPath) {
13061
13145
  function pathsInUidCache(projectPath) {
13062
13146
  let data;
13063
13147
  try {
13064
- data = readFileSync8(join11(projectPath, ".godot", "uid_cache.bin"));
13148
+ data = readFileSync9(join11(projectPath, ".godot", "uid_cache.bin"));
13065
13149
  } catch {
13066
13150
  return null;
13067
13151
  }
@@ -13123,7 +13207,7 @@ function projectTexts(projectPath) {
13123
13207
  if (!entry.isFile() || BINARY_EXTENSIONS.has(extension)) {
13124
13208
  continue;
13125
13209
  }
13126
- const bytes = readFileSync8(absolute);
13210
+ const bytes = readFileSync9(absolute);
13127
13211
  if (!isBinary(bytes)) {
13128
13212
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
13129
13213
  }
@@ -13153,7 +13237,7 @@ function ignoredTexts(projectPath) {
13153
13237
  if (!under || !entry.isFile() || BINARY_EXTENSIONS.has(extension) || entry.name === ".gdignore") {
13154
13238
  continue;
13155
13239
  }
13156
- const bytes = readFileSync8(absolute);
13240
+ const bytes = readFileSync9(absolute);
13157
13241
  if (!isBinary(bytes)) {
13158
13242
  found.push({ path: `res://${spelled}`, absolute, text: bytes.toString("utf8") });
13159
13243
  }
@@ -13190,7 +13274,7 @@ function searchProject(projectPath, options) {
13190
13274
  };
13191
13275
  const regex = options.regex ? compiledQuery(options.query, options.caseSensitive) : null;
13192
13276
  const needle = options.caseSensitive ? options.query : options.query.toLowerCase();
13193
- const full = () => result.summary.truncated;
13277
+ let shown = 0;
13194
13278
  const notSearched = [];
13195
13279
  const met = new Set;
13196
13280
  const searchFile = (entryPath, name) => {
@@ -13201,28 +13285,33 @@ function searchProject(projectPath, options) {
13201
13285
  if (extensions !== null && !extensions.has(extension)) {
13202
13286
  return;
13203
13287
  }
13204
- const bytes = readFileSync8(entryPath);
13288
+ const bytes = readFileSync9(entryPath);
13205
13289
  if (extensions === null && isBinary(bytes)) {
13206
13290
  return;
13207
13291
  }
13208
13292
  result.summary.files_searched += 1;
13209
13293
  const matches = [];
13294
+ let matched = false;
13210
13295
  for (const [index, line] of bytes.toString("utf8").split(/\r?\n/).entries()) {
13211
13296
  const found = regex ? regex.exec(line) : plainMatch(line, needle, options.caseSensitive);
13212
13297
  if (found === null) {
13213
13298
  continue;
13214
13299
  }
13215
- if (result.summary.total_matches >= options.maxResults) {
13300
+ matched = true;
13301
+ result.summary.total_matches += 1;
13302
+ if (shown >= options.maxResults) {
13216
13303
  result.summary.truncated = true;
13217
- break;
13304
+ continue;
13218
13305
  }
13306
+ shown += 1;
13219
13307
  matches.push({ line: index + 1, content: line.trim(), match: found[0] });
13220
- result.summary.total_matches += 1;
13308
+ }
13309
+ if (matched) {
13310
+ result.summary.files_with_matches += 1;
13221
13311
  }
13222
13312
  if (matches.length > 0) {
13223
13313
  const relativePath = entryPath.slice(projectPath.length + 1).replace(/\\/g, "/");
13224
13314
  result.results.push({ file: `res://${relativePath}`, matches });
13225
- result.summary.files_with_matches += 1;
13226
13315
  }
13227
13316
  };
13228
13317
  const visit = (directory, named = false) => {
@@ -13233,9 +13322,6 @@ function searchProject(projectPath, options) {
13233
13322
  return;
13234
13323
  }
13235
13324
  for (const entry of readdirSync6(directory, { withFileTypes: true })) {
13236
- if (full()) {
13237
- return;
13238
- }
13239
13325
  if (skipped(entry.name)) {
13240
13326
  continue;
13241
13327
  }
@@ -13315,7 +13401,7 @@ var init_project_scan = __esm(() => {
13315
13401
 
13316
13402
  // src/import-freshness.ts
13317
13403
  import { createHash as createHash2 } from "node:crypto";
13318
- 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";
13319
13405
  import { join as join12 } from "node:path";
13320
13406
  function md5Of(file) {
13321
13407
  let stats;
@@ -13330,7 +13416,7 @@ function md5Of(file) {
13330
13416
  }
13331
13417
  let md5;
13332
13418
  try {
13333
- md5 = createHash2("md5").update(readFileSync9(file)).digest("hex");
13419
+ md5 = createHash2("md5").update(readFileSync10(file)).digest("hex");
13334
13420
  } catch {
13335
13421
  return null;
13336
13422
  }
@@ -13356,7 +13442,7 @@ function sectionOf(text, section) {
13356
13442
  function unquoted(value) {
13357
13443
  return value === undefined ? "" : value.replace(/^"(.*)"$/, "$1");
13358
13444
  }
13359
- function listed(value) {
13445
+ function listed2(value) {
13360
13446
  return [...(value ?? "").matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((match) => match[1] ?? "");
13361
13447
  }
13362
13448
  function importedIntoTheProject(projectPath, resPaths) {
@@ -13364,11 +13450,11 @@ function importedIntoTheProject(projectPath, resPaths) {
13364
13450
  for (const resPath of resPaths) {
13365
13451
  let sidecar;
13366
13452
  try {
13367
- sidecar = readFileSync9(`${onDisk(projectPath, resPath)}.import`, "utf8");
13453
+ sidecar = readFileSync10(`${onDisk(projectPath, resPath)}.import`, "utf8");
13368
13454
  } catch {
13369
13455
  continue;
13370
13456
  }
13371
- 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/"));
13372
13458
  if (outputs.length > 0) {
13373
13459
  wrote[resPath] = outputs;
13374
13460
  }
@@ -13384,7 +13470,7 @@ function behind(projectPath, resPath, sidecar) {
13384
13470
  if (importer === "keep" || importer === "skip") {
13385
13471
  return null;
13386
13472
  }
13387
- const outputs = listed(sectionOf(sidecar, "deps").get("dest_files"));
13473
+ const outputs = listed2(sectionOf(sidecar, "deps").get("dest_files"));
13388
13474
  if (outputs.some((output) => !existsSync10(onDisk(projectPath, output)))) {
13389
13475
  return "its imported output is not on disk";
13390
13476
  }
@@ -13392,7 +13478,7 @@ function behind(projectPath, resPath, sidecar) {
13392
13478
  const record = join12(projectPath, ".godot", "imported", `${name}-${createHash2("md5").update(resPath, "utf8").digest("hex")}.md5`);
13393
13479
  let recorded = "";
13394
13480
  try {
13395
- 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] ?? "";
13396
13482
  } catch {
13397
13483
  recorded = "";
13398
13484
  }
@@ -13434,7 +13520,7 @@ function staleImports(projectPath) {
13434
13520
  }
13435
13521
  let sidecar;
13436
13522
  try {
13437
- sidecar = readFileSync9(join12(directory, entry.name), "utf8");
13523
+ sidecar = readFileSync10(join12(directory, entry.name), "utf8");
13438
13524
  } catch {
13439
13525
  continue;
13440
13526
  }
@@ -13498,7 +13584,7 @@ import {
13498
13584
  existsSync as existsSync11,
13499
13585
  mkdirSync as mkdirSync4,
13500
13586
  readdirSync as readdirSync8,
13501
- readFileSync as readFileSync10,
13587
+ readFileSync as readFileSync11,
13502
13588
  renameSync as renameSync2,
13503
13589
  unlinkSync,
13504
13590
  writeFileSync as writeFileSync6
@@ -13614,7 +13700,7 @@ function resolveResource(projectPath, relativeTo, path) {
13614
13700
  }
13615
13701
  function readOrNull(path) {
13616
13702
  try {
13617
- return readFileSync10(path, "utf8");
13703
+ return readFileSync11(path, "utf8");
13618
13704
  } catch {
13619
13705
  return null;
13620
13706
  }
@@ -13623,7 +13709,7 @@ var init_library_copies = () => {};
13623
13709
 
13624
13710
  // src/reimport.ts
13625
13711
  import { createHash as createHash3 } from "node:crypto";
13626
- 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";
13627
13713
  import { basename as basename7, join as join14 } from "node:path";
13628
13714
  function forceNextImport(projectPath, resourcePath) {
13629
13715
  const relative = resourcePath.replace(/^res:\/\//, "");
@@ -13632,7 +13718,7 @@ function forceNextImport(projectPath, resourcePath) {
13632
13718
  return;
13633
13719
  }
13634
13720
  const { atime, mtimeMs } = statSync11(sidecar);
13635
- writeFileSync7(sidecar, readFileSync11(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
13721
+ writeFileSync7(sidecar, readFileSync12(sidecar, "utf8").replace(/^valid=false\r?\n/m, ""));
13636
13722
  utimesSync(sidecar, atime, new Date(mtimeMs + 2000));
13637
13723
  rmSync7(importRecord(projectPath, resourcePath), { force: true });
13638
13724
  }
@@ -13858,7 +13944,7 @@ var init_script_origins = __esm(() => {
13858
13944
  });
13859
13945
 
13860
13946
  // src/headless.ts
13861
- 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";
13862
13948
  import { dirname as dirname7, join as join17 } from "node:path";
13863
13949
  function snakeCased(params) {
13864
13950
  const result = emptyRecord();
@@ -14149,7 +14235,7 @@ function answerIn(file) {
14149
14235
  if (!existsSync14(file)) {
14150
14236
  return { kind: "none" };
14151
14237
  }
14152
- const text = readFileSync12(file, "utf8");
14238
+ const text = readFileSync13(file, "utf8");
14153
14239
  try {
14154
14240
  const parsed = JSON.parse(text);
14155
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)}` };
@@ -14174,7 +14260,7 @@ var init_headless = __esm(() => {
14174
14260
  });
14175
14261
 
14176
14262
  // src/update-check.ts
14177
- 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";
14178
14264
  import { homedir as homedir5, tmpdir as tmpdir4 } from "node:os";
14179
14265
  import { join as join18 } from "node:path";
14180
14266
  function packageUrl(root) {
@@ -14219,7 +14305,7 @@ function readCache(path) {
14219
14305
  if (!existsSync15(path)) {
14220
14306
  return null;
14221
14307
  }
14222
- const parsed = JSON.parse(readFileSync13(path, "utf8"));
14308
+ const parsed = JSON.parse(readFileSync14(path, "utf8"));
14223
14309
  if (typeof parsed !== "object" || parsed === null) {
14224
14310
  return null;
14225
14311
  }
@@ -14367,7 +14453,7 @@ var init_update_check = __esm(() => {
14367
14453
  });
14368
14454
 
14369
14455
  // src/server-version.ts
14370
- import { readFileSync as readFileSync14 } from "node:fs";
14456
+ import { readFileSync as readFileSync15 } from "node:fs";
14371
14457
  function staleAddon(addonDigest, shippedDigest) {
14372
14458
  return addonDigest !== shippedDigest;
14373
14459
  }
@@ -14426,7 +14512,7 @@ var init_server_version = __esm(() => {
14426
14512
  DEBUG_MODE = process.env["GDHARNESS_DEBUG"] === "1";
14427
14513
  SERVER_VERSION = (() => {
14428
14514
  try {
14429
- 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"));
14430
14516
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
14431
14517
  } catch {
14432
14518
  return "0.0.0";
@@ -15537,7 +15623,7 @@ var init_lsp_client = __esm(() => {
15537
15623
  });
15538
15624
 
15539
15625
  // src/resources.ts
15540
- import { readFileSync as readFileSync16, realpathSync as realpathSync3 } from "node:fs";
15626
+ import { readFileSync as readFileSync17, realpathSync as realpathSync3 } from "node:fs";
15541
15627
  import { extname as extname2, resolve as resolve8 } from "node:path";
15542
15628
  function ensureProjectPath(getProjectPath) {
15543
15629
  const projectPath = getProjectPath();
@@ -15733,7 +15819,7 @@ function readResourceText(uri, getProjectPath) {
15733
15819
  const parsedUri = parseGodotUri(uri);
15734
15820
  if (parsedUri.kind === "project-info") {
15735
15821
  const projectFilePath = resolveProjectFile(projectPath, "project.godot");
15736
- const rawProject = readFileSync16(projectFilePath, "utf-8");
15822
+ const rawProject = readFileSync17(projectFilePath, "utf-8");
15737
15823
  const parsedProject = parseProjectGodot(rawProject);
15738
15824
  return {
15739
15825
  mimeType: "application/json",
@@ -15742,7 +15828,7 @@ function readResourceText(uri, getProjectPath) {
15742
15828
  }
15743
15829
  const filePath = resolveProjectFile(projectPath, parsedUri.resourcePath);
15744
15830
  ensureAllowedExtension(parsedUri.kind, filePath);
15745
- const text = readFileSync16(filePath, "utf-8");
15831
+ const text = readFileSync17(filePath, "utf-8");
15746
15832
  return {
15747
15833
  mimeType: parsedUri.kind === "script" ? "text/x-gdscript" : "text/plain",
15748
15834
  text
@@ -15889,7 +15975,7 @@ import {
15889
15975
  existsSync as existsSync18,
15890
15976
  mkdirSync as mkdirSync6,
15891
15977
  readdirSync as readdirSync11,
15892
- readFileSync as readFileSync17,
15978
+ readFileSync as readFileSync18,
15893
15979
  rmdirSync,
15894
15980
  rmSync as rmSync9,
15895
15981
  statSync as statSync12,
@@ -15923,7 +16009,7 @@ function addonsDigest(names, from) {
15923
16009
  } else {
15924
16010
  hash.update(`${inside}
15925
16011
  `);
15926
- hash.update(readFileSync17(path));
16012
+ hash.update(readFileSync18(path));
15927
16013
  hash.update(`
15928
16014
  `);
15929
16015
  }
@@ -15953,7 +16039,7 @@ function digestMarker(projectPath, addon) {
15953
16039
  if (!existsSync18(marker)) {
15954
16040
  return null;
15955
16041
  }
15956
- const said = readFileSync17(marker, "utf8").trim();
16042
+ const said = readFileSync18(marker, "utf8").trim();
15957
16043
  return said === "" ? null : said;
15958
16044
  }
15959
16045
  function shippedAddonsDirectory() {
@@ -15970,7 +16056,7 @@ function loaderState(projectPath) {
15970
16056
  if (!existsSync18(copy)) {
15971
16057
  return "missing";
15972
16058
  }
15973
- const lines = (path) => readFileSync17(path, "utf8").replaceAll(`\r
16059
+ const lines = (path) => readFileSync18(path, "utf8").replaceAll(`\r
15974
16060
  `, `
15975
16061
  `);
15976
16062
  return lines(copy) === lines(shippedLoader()) ? "current" : "differs";
@@ -15981,7 +16067,7 @@ function writeLoader(projectPath) {
15981
16067
  }
15982
16068
  const copy = join21(projectPath, ...RUNTIME_AUTOLOAD.path.split("/"));
15983
16069
  mkdirSync6(dirname9(copy), { recursive: true });
15984
- writeFileSync10(copy, readFileSync17(shippedLoader()));
16070
+ writeFileSync10(copy, readFileSync18(shippedLoader()));
15985
16071
  return true;
15986
16072
  }
15987
16073
  function removeLoader(projectPath) {
@@ -16040,7 +16126,7 @@ function installedAddonVersion(projectPath) {
16040
16126
  if (!existsSync18(marker)) {
16041
16127
  return null;
16042
16128
  }
16043
- const said = readFileSync17(marker, "utf8").trim();
16129
+ const said = readFileSync18(marker, "utf8").trim();
16044
16130
  return said === "" ? null : said;
16045
16131
  }
16046
16132
  function removeAddons(projectPath) {
@@ -16095,7 +16181,7 @@ function loaderAmong(projectPath, named) {
16095
16181
  }
16096
16182
  let source = null;
16097
16183
  try {
16098
- source = readFileSync17(join21(projectPath, path), "utf8");
16184
+ source = readFileSync18(join21(projectPath, path), "utf8");
16099
16185
  } catch {}
16100
16186
  if (source?.includes(RUNTIME_SCRIPT)) {
16101
16187
  return { name: entry, path, exists: true };
@@ -16154,7 +16240,7 @@ function inspectProject(projectPath) {
16154
16240
  const directory = join21(projectPath, "addons", name);
16155
16241
  const marker = join21(directory, VERSION_MARKER);
16156
16242
  const installed = existsSync18(directory);
16157
- const version = existsSync18(marker) ? readFileSync17(marker, "utf8").trim() : null;
16243
+ const version = existsSync18(marker) ? readFileSync18(marker, "utf8").trim() : null;
16158
16244
  const current = version === SERVER_VERSION;
16159
16245
  if (!installed) {
16160
16246
  problems.push(`addons/${name} is not installed; run gdharness setup`);
@@ -16163,7 +16249,7 @@ function inspectProject(projectPath) {
16163
16249
  }
16164
16250
  return { name, installed, version, current };
16165
16251
  });
16166
- const settings = parseProjectGodot(readFileSync17(join21(projectPath, "project.godot"), "utf8"));
16252
+ const settings = parseProjectGodot(readFileSync18(join21(projectPath, "project.godot"), "utf8"));
16167
16253
  const pluginsEnabled = enabledPlugins(settings);
16168
16254
  for (const name of EDITOR_PLUGINS) {
16169
16255
  if (!pluginsEnabled.includes(name)) {
@@ -16472,7 +16558,7 @@ var init_tool_definitions = __esm(() => {
16472
16558
  position: {
16473
16559
  type: "string",
16474
16560
  enum: ["end", "after_ready", "after_init"],
16475
- description: "Functions: where to insert. end, the default, is the end of the file; after_ready and after_init are after _ready or _init, above the comments and annotation lines that open the function after it, and the end when nothing follows. A function goes in with two blank lines on each side, as gdformat writes them."
16561
+ description: "Functions: where to insert. end, the default, is the end of the file; after_ready and after_init are after _ready or _init, above the comments and annotation lines that open the function after it, and the end when nothing follows. A function goes in with two blank lines on each side, as gdstyle and gdformat write them."
16476
16562
  }
16477
16563
  },
16478
16564
  required: ["type", "name"],
@@ -16594,7 +16680,11 @@ var init_tool_definitions = __esm(() => {
16594
16680
  ops: ["set"],
16595
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.'
16596
16682
  },
16597
- 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
+ },
16598
16688
  path: {
16599
16689
  type: "string",
16600
16690
  ops: ["add_autoload"],
@@ -16748,7 +16838,7 @@ var init_tool_definitions = __esm(() => {
16748
16838
  maxResults: {
16749
16839
  type: "integer",
16750
16840
  minimum: 1,
16751
- 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."
16752
16842
  }
16753
16843
  },
16754
16844
  requires: ["projectPath", "query"]
@@ -16787,7 +16877,7 @@ var init_tool_definitions = __esm(() => {
16787
16877
  name: "project_import",
16788
16878
  title: "Asset imports",
16789
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.",
16790
- 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.",
16791
16881
  parameters: {
16792
16882
  projectPath: PROJECT_PATH,
16793
16883
  resourcePath: { ...RESOURCE_PATH, ops: ["status", "options", "set_options", "uid", "reimport"] },
@@ -16849,7 +16939,7 @@ var init_tool_definitions = __esm(() => {
16849
16939
  },
16850
16940
  refresh_classes: {
16851
16941
  effect: "destructive",
16852
- 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',
16853
16943
  requires: []
16854
16944
  }
16855
16945
  }
@@ -16857,7 +16947,7 @@ var init_tool_definitions = __esm(() => {
16857
16947
  {
16858
16948
  name: "project_export",
16859
16949
  title: "Export",
16860
- 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.",
16861
16951
  parameters: {
16862
16952
  projectPath: PROJECT_PATH,
16863
16953
  preset: {
@@ -16941,13 +17031,13 @@ var init_tool_definitions = __esm(() => {
16941
17031
  title: "Run tests",
16942
17032
  effect: "additive",
16943
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.",
16944
- 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,
16945
17035
  parameters: {
16946
17036
  projectPath: PROJECT_PATH,
16947
17037
  path: {
16948
17038
  type: ["string", "array"],
16949
17039
  items: { type: "string" },
16950
- description: 'A test directory or suite file inside the project, or a list of them, all run on one engine: a named set of suites boots once rather than once per suite. An entry given twice, or a suite inside a directory also listed, runs once. An entry "suite.gd:test_name" runs that case alone, as gdharness test reads it and as tests runs it, so it cannot share the list with a whole suite or a directory. An entry that is not there is left out and named under missing, the rest run, and such a run is never called passed; when no entry is there the run is refused before the engine starts, naming each one. A script named that declares no test_ function at the top level is refused the same way, and so is a directory holding no suite. A script that declares test_ functions and does not extend GdUnitTestSuite, which gdUnit4 never runs and never mentions, is refused when named, and under a directory run whole it is named under notSuites and the run is not called passed. Default tests and test, those the project has, which is where gdharness test looks.'
17040
+ description: 'A test directory or suite file inside the project, or a list of them, all run on one engine: a named set of suites boots once rather than once per suite. An entry given twice, or a suite inside a directory also listed, runs once. An entry "suite.gd:test_name" runs that case alone, as gdharness test reads it and as tests runs it, so it cannot share the list with a whole suite or a directory. An entry that is not there is left out and named under missing, the rest run, and such a run is never called passed; when no entry is there the run is refused before the engine starts, naming each one. A script named that declares no test_ function at the top level is refused the same way, and so is a directory holding no suite. A script that declares test_ functions and does not extend GdUnitTestSuite, which gdUnit4 never runs and never mentions, is refused when named, and under a directory run whole it is named under notSuites and the run is not called passed. A case declared there that gdUnit4 never ran, in a suite that passed, comes back under testsNotRun, and a row it skipped for data that does not fit the case under rowsMismatched with the reason gdUnit4 gave; neither run is called passed. Default tests and test, those the project has, which is where gdharness test looks.'
16951
17041
  },
16952
17042
  ignore: {
16953
17043
  type: "array",
@@ -17476,7 +17566,7 @@ var init_tool_definitions = __esm(() => {
17476
17566
  name: "script_diagnostics",
17477
17567
  title: "Script diagnostics",
17478
17568
  effect: "read",
17479
- 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.",
17480
17570
  parameters: {
17481
17571
  projectPath: PROJECT_PATH,
17482
17572
  scriptPath: {
@@ -17863,7 +17953,8 @@ var init_tool_definitions = __esm(() => {
17863
17953
  name: "editor_rescan",
17864
17954
  title: "Rescan the project",
17865
17955
  effect: "additive",
17866
- 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.",
17867
17958
  parameters: {
17868
17959
  projectPath: PROJECT_PATH,
17869
17960
  timeoutMs: {
@@ -18521,7 +18612,7 @@ var init_tool_definitions = __esm(() => {
18521
18612
  {
18522
18613
  name: "debug_state",
18523
18614
  title: "Debugger state",
18524
- 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.",
18525
18616
  parameters: {
18526
18617
  frameId: {
18527
18618
  type: "number",
@@ -18662,9 +18753,9 @@ function whatItCanChange(tool) {
18662
18753
  if (destroying.length === 0) {
18663
18754
  return "Adds or starts things, and overwrites and removes nothing itself.";
18664
18755
  }
18665
- 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)}.`;
18666
18757
  }
18667
- function listed2(items) {
18758
+ function listed3(items) {
18668
18759
  const last = items.at(-1) ?? "";
18669
18760
  return items.length === 1 ? last : `${items.slice(0, -1).join(", ")} and ${last}`;
18670
18761
  }
@@ -18722,7 +18813,7 @@ var init_tool_reference = __esm(() => {
18722
18813
  });
18723
18814
 
18724
18815
  // src/suite-plan.ts
18725
- 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";
18726
18817
  import { dirname as dirname11, join as join25, posix as posix3 } from "node:path";
18727
18818
  function within(path, folder) {
18728
18819
  return path === folder || path.startsWith(`${folder}/`);
@@ -18739,7 +18830,7 @@ class ProjectScripts {
18739
18830
  this.projectPath = projectPath;
18740
18831
  let settings = {};
18741
18832
  try {
18742
- settings = parseProjectGodot(readFileSync20(join25(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18833
+ settings = parseProjectGodot(readFileSync21(join25(projectPath, "project.godot"), "utf8"))["gdharness"] ?? {};
18743
18834
  } catch {}
18744
18835
  const [section, key] = [NOT_SEARCHED_SETTING, GUESS_WINDOW_SETTING].map((setting) => setting.slice("gdharness/".length));
18745
18836
  const listed = settings[section ?? ""];
@@ -18854,7 +18945,7 @@ class ProjectScripts {
18854
18945
  }
18855
18946
  function readTimes(file) {
18856
18947
  try {
18857
- const parsed = JSON.parse(readFileSync20(file, "utf8"));
18948
+ const parsed = JSON.parse(readFileSync21(file, "utf8"));
18858
18949
  return typeof parsed === "object" && parsed !== null ? parsed : {};
18859
18950
  } catch {
18860
18951
  return {};
@@ -19137,7 +19228,7 @@ function viewportOf(projectPath) {
19137
19228
  if (!existsSync22(file)) {
19138
19229
  return null;
19139
19230
  }
19140
- const text = readFileSync20(file, "utf8");
19231
+ const text = readFileSync21(file, "utf8");
19141
19232
  const width = /^window\/size\/viewport_width\s*=\s*(\d+)/m.exec(text)?.[1];
19142
19233
  const height = /^window\/size\/viewport_height\s*=\s*(\d+)/m.exec(text)?.[1];
19143
19234
  return width === undefined || height === undefined ? null : { width: Number(width), height: Number(height) };
@@ -40756,13 +40847,13 @@ var init_stdio = __esm(() => {
40756
40847
  });
40757
40848
 
40758
40849
  // src/autoload-failure.ts
40759
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
40850
+ import { existsSync as existsSync24, readFileSync as readFileSync24 } from "node:fs";
40760
40851
  import { join as join28 } from "node:path";
40761
40852
  function scriptsAutoloaded(projectPath) {
40762
40853
  const scripts = new Map;
40763
40854
  let settings;
40764
40855
  try {
40765
- settings = parseProjectGodot(readFileSync23(join28(projectPath, "project.godot"), "utf8"));
40856
+ settings = parseProjectGodot(readFileSync24(join28(projectPath, "project.godot"), "utf8"));
40766
40857
  } catch {
40767
40858
  return scripts;
40768
40859
  }
@@ -40776,7 +40867,7 @@ function scriptsAutoloaded(projectPath) {
40776
40867
  continue;
40777
40868
  }
40778
40869
  try {
40779
- 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)) {
40780
40871
  scripts.set(named[1] ?? "", name);
40781
40872
  }
40782
40873
  } catch {}
@@ -40795,7 +40886,7 @@ var init_autoload_failure = __esm(() => {
40795
40886
  });
40796
40887
 
40797
40888
  // src/runtime-client.ts
40798
- 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";
40799
40890
  import { createConnection as createConnection2 } from "node:net";
40800
40891
  import { tmpdir as tmpdir5 } from "node:os";
40801
40892
  import { dirname as dirname14, join as join29, resolve as resolve11 } from "node:path";
@@ -40850,7 +40941,7 @@ function reportAlreadyInTranscript(transcript, report) {
40850
40941
  function parseAnnouncement(file, pid) {
40851
40942
  let fields;
40852
40943
  try {
40853
- fields = asParams(JSON.parse(readFileSync24(file, "utf8")));
40944
+ fields = asParams(JSON.parse(readFileSync25(file, "utf8")));
40854
40945
  } catch {
40855
40946
  return { kind: "rubbish" };
40856
40947
  }
@@ -40922,7 +41013,7 @@ function discoverRuntimes(directories = runtimeDirectories()) {
40922
41013
  function wentAway(file, pid) {
40923
41014
  let project = "";
40924
41015
  try {
40925
- const fields = asParams(JSON.parse(readFileSync24(file, "utf8")));
41016
+ const fields = asParams(JSON.parse(readFileSync25(file, "utf8")));
40926
41017
  project = readString(readParams(fields, "project") ?? {}, "path") ?? "";
40927
41018
  } catch {
40928
41019
  return;
@@ -41437,14 +41528,14 @@ var init_runtime_client = __esm(() => {
41437
41528
  });
41438
41529
 
41439
41530
  // src/boot-note.ts
41440
- 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";
41441
41532
  import { dirname as dirname15, join as join30 } from "node:path";
41442
41533
  function bootNotePath(projectPath) {
41443
41534
  return join30(projectPath, ".godot", "gdharness-boot.json");
41444
41535
  }
41445
41536
  function readScenes(projectPath) {
41446
41537
  try {
41447
- return asParams(asParams(JSON.parse(readFileSync25(bootNotePath(projectPath), "utf8")))["scenes"]);
41538
+ return asParams(asParams(JSON.parse(readFileSync26(bootNotePath(projectPath), "utf8")))["scenes"]);
41448
41539
  } catch {
41449
41540
  return {};
41450
41541
  }
@@ -41486,7 +41577,7 @@ var init_boot_note = __esm(() => {
41486
41577
  });
41487
41578
 
41488
41579
  // src/breakpoint-note.ts
41489
- 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";
41490
41581
  import { dirname as dirname16, isAbsolute as isAbsolute7, join as join31, relative as relative6 } from "node:path";
41491
41582
  function breakpointNotePath(projectPath) {
41492
41583
  return join31(projectPath, ".godot", "gdharness-breakpoints.json");
@@ -41515,7 +41606,7 @@ function writeBreakpointNote(projectPath, held) {
41515
41606
  }
41516
41607
  function readBreakpointNote(projectPath) {
41517
41608
  try {
41518
- const parsed = JSON.parse(readFileSync26(breakpointNotePath(projectPath), "utf8"));
41609
+ const parsed = JSON.parse(readFileSync27(breakpointNotePath(projectPath), "utf8"));
41519
41610
  if (typeof parsed !== "object" || parsed === null || !Array.isArray(parsed.breakpoints)) {
41520
41611
  return [];
41521
41612
  }
@@ -41548,14 +41639,14 @@ var init_breakpoint_note = __esm(() => {
41548
41639
  });
41549
41640
 
41550
41641
  // src/class-note.ts
41551
- import { mkdirSync as mkdirSync13, readFileSync as readFileSync27 } from "node:fs";
41642
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync28 } from "node:fs";
41552
41643
  import { dirname as dirname17, join as join32 } from "node:path";
41553
41644
  function classNotePath(projectPath) {
41554
41645
  return join32(projectPath, ".godot", "gdharness-classes.json");
41555
41646
  }
41556
41647
  function readClassNote(projectPath) {
41557
41648
  try {
41558
- const fields = asParams(JSON.parse(readFileSync27(classNotePath(projectPath), "utf8")));
41649
+ const fields = asParams(JSON.parse(readFileSync28(classNotePath(projectPath), "utf8")));
41559
41650
  const rebuiltWith = readArray(fields, "rebuiltWith");
41560
41651
  if (rebuiltWith === undefined || !rebuiltWith.every((name) => typeof name === "string")) {
41561
41652
  return null;
@@ -41597,7 +41688,7 @@ var init_class_note = __esm(() => {
41597
41688
  });
41598
41689
 
41599
41690
  // src/config-pin.ts
41600
- import { existsSync as existsSync26, readFileSync as readFileSync28 } from "node:fs";
41691
+ import { existsSync as existsSync26, readFileSync as readFileSync29 } from "node:fs";
41601
41692
  import { join as join33 } from "node:path";
41602
41693
  function versionsNamedInConfig(projectPath) {
41603
41694
  const config = join33(projectPath, ".mcp.json");
@@ -41606,7 +41697,7 @@ function versionsNamedInConfig(projectPath) {
41606
41697
  }
41607
41698
  let text;
41608
41699
  try {
41609
- text = readFileSync28(config, "utf8");
41700
+ text = readFileSync29(config, "utf8");
41610
41701
  JSON.parse(text);
41611
41702
  } catch {
41612
41703
  return [];
@@ -41627,7 +41718,7 @@ var init_config_pin = __esm(() => {
41627
41718
  });
41628
41719
 
41629
41720
  // src/cut-test-run.ts
41630
- 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";
41631
41722
  import { dirname as dirname18, join as join34 } from "node:path";
41632
41723
  function keptAt(projectPath) {
41633
41724
  return join34(projectPath, ".godot", "gdharness-reports", "cut-run.json");
@@ -41646,7 +41737,7 @@ function takeCutTestRun(projectPath) {
41646
41737
  return null;
41647
41738
  }
41648
41739
  try {
41649
- const run = JSON.parse(readFileSync29(path, "utf8"));
41740
+ const run = JSON.parse(readFileSync30(path, "utf8"));
41650
41741
  rmSync14(path, { force: true });
41651
41742
  return typeof run.cutAt === "string" && Array.isArray(run.paths) && run.paths.every((path) => typeof path === "string") ? run : null;
41652
41743
  } catch {
@@ -42382,7 +42473,7 @@ import {
42382
42473
  existsSync as existsSync28,
42383
42474
  mkdirSync as mkdirSync15,
42384
42475
  openSync as openSync4,
42385
- readFileSync as readFileSync30,
42476
+ readFileSync as readFileSync31,
42386
42477
  readSync as readSync2,
42387
42478
  rmSync as rmSync15,
42388
42479
  statSync as statSync16,
@@ -42397,7 +42488,7 @@ function editorLogNotePath(projectPath) {
42397
42488
  }
42398
42489
  function readEditorLogNote(projectPath) {
42399
42490
  try {
42400
- const fields = asParams(JSON.parse(readFileSync30(editorLogNotePath(projectPath), "utf8")));
42491
+ const fields = asParams(JSON.parse(readFileSync31(editorLogNotePath(projectPath), "utf8")));
42401
42492
  const pid = readNumber(fields, "pid");
42402
42493
  const startedAt = readString(fields, "startedAt");
42403
42494
  const processStartedAt = readNumber(fields, "processStartedAt");
@@ -42501,7 +42592,7 @@ function foldProgress(text) {
42501
42592
  function theFirstOf(path) {
42502
42593
  const whole = statSync16(path).size;
42503
42594
  if (whole <= MOST_CONSOLE_BYTES) {
42504
- return { text: readFileSync30(path, "utf8") };
42595
+ return { text: readFileSync31(path, "utf8") };
42505
42596
  }
42506
42597
  const handle = openSync4(path, "r");
42507
42598
  try {
@@ -42662,7 +42753,7 @@ var init_editor_log = __esm(() => {
42662
42753
  });
42663
42754
 
42664
42755
  // src/rename.ts
42665
- 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";
42666
42757
  import { dirname as dirname20, posix as posix4 } from "node:path";
42667
42758
 
42668
42759
  class Script {
@@ -43367,7 +43458,7 @@ function rewritesOf(plan, texts, move) {
43367
43458
  }
43368
43459
  function applyRewrites(rewrites) {
43369
43460
  for (const rewrite of rewrites) {
43370
- const now = readFileSync31(rewrite.absolute, "utf8");
43461
+ const now = readFileSync32(rewrite.absolute, "utf8");
43371
43462
  if (now !== rewrite.before) {
43372
43463
  throw new Error(`${rewrite.path} changed on disk while the rename was being worked out; nothing was written`);
43373
43464
  }
@@ -43457,18 +43548,18 @@ function onDiskSpelling(path) {
43457
43548
  }
43458
43549
  }
43459
43550
  function usedAsIdentifier(scripts, name, most) {
43460
- const found = [];
43551
+ const found = new Set;
43461
43552
  for (const script of scripts.values()) {
43462
43553
  for (const occurrence of script.occurrences(name)) {
43463
43554
  if (occurrence.kind === "code" && !occurrence.afterDot) {
43464
- found.push(`${script.path}:${script.lines.positionOf(occurrence.offset).line + 1}`);
43465
- if (found.length >= most) {
43466
- return found;
43555
+ found.add(`${script.path}:${script.lines.positionOf(occurrence.offset).line + 1}`);
43556
+ if (found.size >= most) {
43557
+ return [...found];
43467
43558
  }
43468
43559
  }
43469
43560
  }
43470
43561
  }
43471
- return found;
43562
+ return [...found];
43472
43563
  }
43473
43564
  function autoloadNames(texts) {
43474
43565
  const settings = texts.find((file) => file.path === "res://project.godot");
@@ -43532,7 +43623,7 @@ async function renameSymbol(request, services) {
43532
43623
  conflicts.push(`${newName} is already an autoload in project.godot`);
43533
43624
  }
43534
43625
  for (const place of usedAsIdentifier(scripts, newName, 10)) {
43535
- conflicts.push(`${newName} is already an identifier at ${place}`);
43626
+ conflicts.push(`${newName} is already used in code at ${place}`);
43536
43627
  }
43537
43628
  const taken = (await services.namesTaken([newName], null))[newName];
43538
43629
  if (taken !== undefined && taken.as.length > 0) {
@@ -43935,7 +44026,7 @@ import {
43935
44026
  existsSync as existsSync31,
43936
44027
  mkdirSync as mkdirSync18,
43937
44028
  readdirSync as readdirSync17,
43938
- readFileSync as readFileSync32,
44029
+ readFileSync as readFileSync33,
43939
44030
  renameSync as renameSync4,
43940
44031
  statSync as statSync17,
43941
44032
  writeFileSync as writeFileSync19
@@ -44095,7 +44186,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44095
44186
  plan = mentionIgnored(plan, ignoredTexts(projectPath), null, [from]);
44096
44187
  const binaries = [];
44097
44188
  for (const path of binaryResources(projectPath)) {
44098
- const bytes = readFileSync32(join37(projectPath, path.slice("res://".length)));
44189
+ const bytes = readFileSync33(join37(projectPath, path.slice("res://".length)));
44099
44190
  const names = [...moves.keys()].find((moving) => bytes.includes(moving));
44100
44191
  if (names !== undefined) {
44101
44192
  binaries.push({
@@ -44156,7 +44247,7 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
44156
44247
  }
44157
44248
  function applyFileMove(outcome) {
44158
44249
  for (const rewrite of outcome.inPlace) {
44159
- if (readFileSync32(rewrite.absolute, "utf8") !== rewrite.before) {
44250
+ if (readFileSync33(rewrite.absolute, "utf8") !== rewrite.before) {
44160
44251
  throw new Error(`${rewrite.path} changed on disk while the move was being worked out; nothing was written`);
44161
44252
  }
44162
44253
  }
@@ -44164,7 +44255,7 @@ function applyFileMove(outcome) {
44164
44255
  if (!existsSync31(file.absoluteFrom)) {
44165
44256
  throw new Error(`${file.from} is gone from disk; nothing was written`);
44166
44257
  }
44167
- if (file.text !== null && readFileSync32(file.absoluteFrom, "utf8") !== file.text.before) {
44258
+ if (file.text !== null && readFileSync33(file.absoluteFrom, "utf8") !== file.text.before) {
44168
44259
  throw new Error(`${file.from} changed on disk while the move was being worked out; nothing was written`);
44169
44260
  }
44170
44261
  if (existsSync31(file.absoluteTo) && !sameFile(file.absoluteFrom, file.absoluteTo)) {
@@ -48278,7 +48369,7 @@ var init_prompts = __esm(() => {
48278
48369
  });
48279
48370
 
48280
48371
  // src/restart-note.ts
48281
- 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";
48282
48373
  import { dirname as dirname24, join as join39 } from "node:path";
48283
48374
  function restartNotePath(projectPath) {
48284
48375
  return join39(projectPath, ".godot", "gdharness-restart.json");
@@ -48295,7 +48386,7 @@ function noteRestartBegun(note) {
48295
48386
  }
48296
48387
  function restartOwed(projectPath) {
48297
48388
  try {
48298
- const parsed = JSON.parse(readFileSync33(restartNotePath(projectPath), "utf8"));
48389
+ const parsed = JSON.parse(readFileSync34(restartNotePath(projectPath), "utf8"));
48299
48390
  if (typeof parsed !== "object" || parsed === null) {
48300
48391
  return null;
48301
48392
  }
@@ -48321,7 +48412,7 @@ var init_restart_note = __esm(() => {
48321
48412
 
48322
48413
  // src/run-record.ts
48323
48414
  import { createHash as createHash7 } from "node:crypto";
48324
- 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";
48325
48416
  import { basename as basename11, join as join40, resolve as resolve13 } from "node:path";
48326
48417
  function runsDirectory() {
48327
48418
  return join40(runtimeDirectory(), "runs");
@@ -48345,7 +48436,7 @@ function writeLatestRun(servedBy, latest) {
48345
48436
  }
48346
48437
  function readLatestRun(servedBy, directory = runtimeDirectory()) {
48347
48438
  try {
48348
- const fields = JSON.parse(readFileSync34(latestRunPath(servedBy, directory), "utf8"));
48439
+ const fields = JSON.parse(readFileSync35(latestRunPath(servedBy, directory), "utf8"));
48349
48440
  if (typeof fields !== "object" || fields === null) {
48350
48441
  return null;
48351
48442
  }
@@ -48394,7 +48485,7 @@ function readEditorRunNote(projectPath) {
48394
48485
  function editorNoteAt(path) {
48395
48486
  let parsed;
48396
48487
  try {
48397
- parsed = JSON.parse(readFileSync34(path, "utf8"));
48488
+ parsed = JSON.parse(readFileSync35(path, "utf8"));
48398
48489
  } catch {
48399
48490
  return null;
48400
48491
  }
@@ -48444,7 +48535,7 @@ function everyRunRecord() {
48444
48535
  function recordAt(path) {
48445
48536
  let parsed;
48446
48537
  try {
48447
- parsed = JSON.parse(readFileSync34(path, "utf8"));
48538
+ parsed = JSON.parse(readFileSync35(path, "utf8"));
48448
48539
  } catch {
48449
48540
  return null;
48450
48541
  }
@@ -48525,7 +48616,7 @@ async function runningAs(pid) {
48525
48616
  const began = await startedAt(pid, null);
48526
48617
  const when = began === null ? {} : { startedAt: began };
48527
48618
  if (process.platform === "linux") {
48528
- const raw = readFileSync34(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
48619
+ const raw = readFileSync35(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ").trim();
48529
48620
  return raw === "" ? null : { kind: "commandLine", text: raw, ...when };
48530
48621
  }
48531
48622
  const args = (await runHidden("ps", ["-p", String(pid), "-o", "args="], { timeout: 15000 })).stdout.trim();
@@ -48721,10 +48812,10 @@ var init_run_record = __esm(() => {
48721
48812
  });
48722
48813
 
48723
48814
  // src/script-profile.ts
48724
- import { readFileSync as readFileSync35 } from "node:fs";
48815
+ import { readFileSync as readFileSync36 } from "node:fs";
48725
48816
  function readProfileFile(path) {
48726
48817
  try {
48727
- const parsed = JSON.parse(readFileSync35(path, "utf8"));
48818
+ const parsed = JSON.parse(readFileSync36(path, "utf8"));
48728
48819
  return typeof parsed === "object" && typeof parsed.connected === "boolean" ? parsed : null;
48729
48820
  } catch {
48730
48821
  return null;
@@ -48782,7 +48873,7 @@ function writeProfileMark(path, mark) {
48782
48873
  }
48783
48874
  function readProfileMark(path) {
48784
48875
  try {
48785
- const parsed = JSON.parse(readFileSync35(path, "utf8"));
48876
+ const parsed = JSON.parse(readFileSync36(path, "utf8"));
48786
48877
  return typeof parsed === "object" && typeof parsed.totals === "object" ? parsed : null;
48787
48878
  } catch {
48788
48879
  return null;
@@ -49114,7 +49205,7 @@ import {
49114
49205
  mkdirSync as mkdirSync23,
49115
49206
  openSync as openSync6,
49116
49207
  readdirSync as readdirSync19,
49117
- readFileSync as readFileSync36,
49208
+ readFileSync as readFileSync37,
49118
49209
  readSync as readSync3,
49119
49210
  realpathSync as realpathSync5,
49120
49211
  rmdirSync as rmdirSync2,
@@ -49135,9 +49226,9 @@ function greetingWaitMs() {
49135
49226
  return waitFromEnv("GDHARNESS_GREETING_WAIT_MS", 1e4);
49136
49227
  }
49137
49228
  function pictureToSend(pngPath, jpegPath) {
49138
- const png = readFileSync36(pngPath);
49229
+ const png = readFileSync37(pngPath);
49139
49230
  const beside = pngPath.replace(/\.png$/, ".jpg");
49140
- 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;
49141
49232
  const sent = jpeg !== null && jpeg.length < png.length ? jpeg : png;
49142
49233
  return {
49143
49234
  data: sent.toString("base64"),
@@ -49616,7 +49707,7 @@ function endedToStartThis(ended) {
49616
49707
  return ` ${which} was ended to start this one; its output is no longer what editor_output answers about.`;
49617
49708
  }
49618
49709
  function lineNothingStopsOn(absolutePath, relativePath, line) {
49619
- const text = readFileSync36(absolutePath, "utf8");
49710
+ const text = readFileSync37(absolutePath, "utf8");
49620
49711
  const lines = text === "" ? 0 : text.split(`
49621
49712
  `).length - (text.endsWith(`
49622
49713
  `) ? 1 : 0);
@@ -49827,7 +49918,7 @@ function notRunNote(notRun, failFast) {
49827
49918
  function presetsIn(projectPath) {
49828
49919
  let text;
49829
49920
  try {
49830
- text = readFileSync36(join43(projectPath, "export_presets.cfg"), "utf8");
49921
+ text = readFileSync37(join43(projectPath, "export_presets.cfg"), "utf8");
49831
49922
  } catch {
49832
49923
  return null;
49833
49924
  }
@@ -49977,7 +50068,7 @@ function rewriteBusNamed(projectPath, from, to) {
49977
50068
  const rewritten = [];
49978
50069
  for (const file of projectFiles(projectPath, [".tscn", ".tres"])) {
49979
50070
  const absolute = join43(projectPath, file);
49980
- const lines = readFileSync36(absolute, "utf8").split(`
50071
+ const lines = readFileSync37(absolute, "utf8").split(`
49981
50072
  `);
49982
50073
  const at = [];
49983
50074
  const after = lines.map((line, index) => {
@@ -49996,7 +50087,7 @@ function rewriteBusNamed(projectPath, from, to) {
49996
50087
  }
49997
50088
  const namedInScripts = [];
49998
50089
  for (const file of projectFiles(projectPath, [".gd"])) {
49999
- readFileSync36(join43(projectPath, file), "utf8").split(`
50090
+ readFileSync37(join43(projectPath, file), "utf8").split(`
50000
50091
  `).forEach((line, index) => {
50001
50092
  if (line.includes(`"${from}"`)) {
50002
50093
  namedInScripts.push({ file: `res://${file}`, line: index + 1, text: line.trim() });
@@ -50281,7 +50372,7 @@ function profileFrameFunctions(projectPath) {
50281
50372
  let projectGodot = null;
50282
50373
  if (projectPath !== null) {
50283
50374
  try {
50284
- projectGodot = readFileSync36(join43(projectPath, "project.godot"), "utf8");
50375
+ projectGodot = readFileSync37(join43(projectPath, "project.godot"), "utf8");
50285
50376
  } catch {}
50286
50377
  }
50287
50378
  return frameFunctionsFor(queuedMessagesOf(projectGodot));
@@ -50305,7 +50396,7 @@ function projectSourceOf(projectPath) {
50305
50396
  let source = null;
50306
50397
  if (inside.ok) {
50307
50398
  try {
50308
- source = readFileSync36(inside.absolutePath, "utf8");
50399
+ source = readFileSync37(inside.absolutePath, "utf8");
50309
50400
  } catch {}
50310
50401
  }
50311
50402
  read.set(script, source);
@@ -50477,7 +50568,7 @@ function foldedEntriesAnswer(log, floor = "warning") {
50477
50568
  }
50478
50569
  function gdUnitFailsOnScriptErrors(projectPath) {
50479
50570
  try {
50480
- 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;
50481
50572
  } catch {
50482
50573
  return true;
50483
50574
  }
@@ -50609,7 +50700,7 @@ function engineChecked(why) {
50609
50700
  return {
50610
50701
  checkedBy: "engine",
50611
50702
  languageServer: why,
50612
- 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."
50613
50704
  };
50614
50705
  }
50615
50706
  function asWarning(raised) {
@@ -50624,11 +50715,11 @@ function isDirectory(path) {
50624
50715
  }
50625
50716
  }
50626
50717
  function mainSceneOf(projectFile) {
50627
- const scene = parseProjectGodot(readFileSync36(projectFile, "utf8"))["application"]?.["run/main_scene"];
50718
+ const scene = parseProjectGodot(readFileSync37(projectFile, "utf8"))["application"]?.["run/main_scene"];
50628
50719
  return typeof scene === "string" && scene !== "" ? scene : null;
50629
50720
  }
50630
50721
  function editorPlaysHeadless(projectFile) {
50631
- const runArgs = parseProjectGodot(readFileSync36(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
50722
+ const runArgs = parseProjectGodot(readFileSync37(projectFile, "utf8"))["editor"]?.["run/main_run_args"];
50632
50723
  return typeof runArgs === "string" && /(?:^|\s)--headless(?:\s|$)/.test(runArgs);
50633
50724
  }
50634
50725
  function ownArguments(params) {
@@ -51081,12 +51172,16 @@ class GodotServer {
51081
51172
  };
51082
51173
  const call = this.callsTaken++;
51083
51174
  this.inFlight.set(call, { tool: spec.name, op: checked.op ?? "", startedAt: Date.now() });
51175
+ const slotWaits = { ms: 0, said: undefined };
51084
51176
  try {
51085
51177
  const answer = await withCallSignal(ctx.mcpReq.signal, () => this.answered(spec.name, checked.op ?? "", args), progress, {
51086
51178
  everyMs: CALL_HEARTBEAT_MS,
51087
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) }
51088
51183
  });
51089
- return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(answer)));
51184
+ return this.withinTheClientsLimit(spec.name, this.withFeedbackNotice(this.withUpdateNotice(waited)));
51090
51185
  } finally {
51091
51186
  this.inFlight.delete(call);
51092
51187
  }
@@ -51765,6 +51860,10 @@ class GodotServer {
51765
51860
  if (kept !== null) {
51766
51861
  return await this.askTheKeptGame(kept.keeper, keptGameRequest(tool, op, args), kept.projectPath ?? "");
51767
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
+ }
51768
51867
  if (tool === "debug_state" && op === "output") {
51769
51868
  return await this.handleDAP("dap_get_output", args);
51770
51869
  }
@@ -52543,7 +52642,7 @@ class GodotServer {
52543
52642
  if (!engine.ok) {
52544
52643
  return engine.response;
52545
52644
  }
52546
- const application = parseProjectGodot(readFileSync36(project.value.file, "utf8"))["application"] ?? {};
52645
+ const application = parseProjectGodot(readFileSync37(project.value.file, "utf8"))["application"] ?? {};
52547
52646
  const name = application["config/name"];
52548
52647
  const mainScene = application["run/main_scene"];
52549
52648
  const info = {
@@ -53094,13 +53193,13 @@ ${platform}`;
53094
53193
  const reportsDir = join43(project.value.path, ".godot", ...ours.split("/"));
53095
53194
  let report = null;
53096
53195
  let reportProblem = null;
53097
- 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);
53098
53197
  const declaredErrors = readExpected(expectedFile);
53099
53198
  try {
53100
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))) : [];
53101
53200
  const newest = written.at(-1);
53102
53201
  if (newest !== undefined) {
53103
- report = parseJUnit(readFileSync36(join43(reportsDir, newest, "results.xml"), "utf8"));
53202
+ report = parseJUnit(readFileSync37(join43(reportsDir, newest, "results.xml"), "utf8"));
53104
53203
  }
53105
53204
  } catch (error) {
53106
53205
  reportProblem = errorMessage(error);
@@ -53287,10 +53386,23 @@ ${error.text}`;
53287
53386
  const testsNotRun = [
53288
53387
  ...new Set((selection.value?.selected ?? []).filter((one) => !report.suites.some((suite) => (suite.path === one.suite || suite.name === basename13(one.suite, ".gd")) && suite.cases.some((ran) => ran.name === one.test || new RegExp(`^${one.test}\\W`).test(ran.name)))).map((one) => `${basename13(one.suite, ".gd")}:${one.test}`))
53289
53388
  ];
53389
+ const casesNotRun = selection.value === null ? asked.flatMap((path) => path.endsWith(".gd") ? projectScripts.suite(path) ?? [] : projectScripts.suitesUnder([path])).flatMap((suite) => {
53390
+ const ran = casePrinted.suites.get(suite.path);
53391
+ if (ran?.closed?.verdict !== "PASSED") {
53392
+ return [];
53393
+ }
53394
+ return suite.cases.filter((func) => !ran.cases.some((one) => one.func === func)).map((func) => `${basename13(suite.path, ".gd")}:${func}`).filter((one) => !ignoreRead.leftOut.includes(one));
53395
+ }) : [];
53396
+ const firstUndeclared = undeclaredErrors[0];
53397
+ const declaredAs = firstUndeclared?.case === undefined ? "" : ` A suite declaring none yet does it as const ${firstUndeclared.warning === true ? "GDHARNESS_EXPECTED_WARNINGS" : "GDHARNESS_EXPECTED_ERRORS"}: Dictionary = ${JSON.stringify({ [funcOf(firstUndeclared.case)]: [firstUndeclared.text] })}, keyed by case function, or by before and after for the suite's own hooks.`;
53398
+ const rowsMismatched = casePrinted.mismatched.map((row) => ({
53399
+ test: `${row.suite} > ${row.name}`,
53400
+ reason: row.reason
53401
+ }));
53290
53402
  return this.jsonTextResponse({
53291
- passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0 && uncaught.length === 0 && undeclaredPrinted.length === 0 && declaredNotPrinted.length === 0 && countedLeaks.length === 0 && missing.length === 0 && testsNotRun.length === 0 && notSuites.length === 0,
53403
+ passed: !timedOut && exitCode === 0 && nothingRan === null && report.tests > 0 && report.failures === 0 && report.errors === 0 && hookFailures.length === 0 && uncaught.length === 0 && undeclaredPrinted.length === 0 && declaredNotPrinted.length === 0 && countedLeaks.length === 0 && missing.length === 0 && testsNotRun.length === 0 && casesNotRun.length === 0 && rowsMismatched.length === 0 && notSuites.length === 0,
53292
53404
  verdict: [
53293
- verdict === "passed" && undeclaredPrinted.length + declaredNotPrinted.length + countedLeaks.length + testsNotRun.length + notSuites.length > 0 ? undefined : verdict,
53405
+ verdict === "passed" && undeclaredPrinted.length + declaredNotPrinted.length + countedLeaks.length + testsNotRun.length + casesNotRun.length + rowsMismatched.length + notSuites.length > 0 ? undefined : verdict,
53294
53406
  ...[false, true].map((warning) => {
53295
53407
  const count = undeclaredPrinted.filter((error) => error.warning === warning).length;
53296
53408
  const word = warning ? "warning" : "error";
@@ -53302,12 +53414,14 @@ ${error.text}`;
53302
53414
  return count > 0 ? `${count === 1 ? `one declared ${word}` : `${String(count)} declared ${word}s`} never printed` : undefined;
53303
53415
  }),
53304
53416
  countedLeaks.length > 0 ? "the engine exited holding what it should have freed" : undefined,
53305
- testsNotRun.length > 0 ? `not run: ${testsNotRun.join(", ")}` : undefined,
53417
+ testsNotRun.length + casesNotRun.length > 0 ? `not run: ${[...testsNotRun, ...casesNotRun].join(", ")}` : undefined,
53418
+ rowsMismatched.length > 0 ? `${rowsMismatched.length === 1 ? "one row" : `${String(rowsMismatched.length)} rows`} skipped for data that does not fit the case` : undefined,
53306
53419
  notSuites.length > 0 ? `${notSuites.length === 1 ? "one script declares" : `${String(notSuites.length)} scripts declare`} cases gdUnit4 never runs` : undefined
53307
53420
  ].filter((part) => part !== undefined).join("; "),
53308
53421
  ...missing.length > 0 ? { missing } : {},
53309
53422
  ...notSuites.length > 0 ? { notSuites } : {},
53310
- ...testsNotRun.length > 0 ? { testsNotRun } : {},
53423
+ ...testsNotRun.length + casesNotRun.length > 0 ? { testsNotRun: [...testsNotRun, ...casesNotRun] } : {},
53424
+ ...rowsMismatched.length > 0 ? { rowsMismatched } : {},
53311
53425
  ...timedOut ? { timedOut, hung, silentForMs } : {},
53312
53426
  exitCode,
53313
53427
  exitSignal: run.exitSignal ?? undefined,
@@ -53339,12 +53453,14 @@ ${error.text}`;
53339
53453
  note: [
53340
53454
  missingNote,
53341
53455
  testsNotRun.length > 0 ? `${testsNotRun.join(", ")} ${testsNotRun.length === 1 ? "was" : "were"} asked for and not run, so the run is not called passed: gdUnit4 runs a test_ function only in a script that extends GdUnitTestSuite, and leaves out a suite whose script does not load.` : undefined,
53456
+ casesNotRun.length > 0 ? `${casesNotRun.join(", ")} ${casesNotRun.length === 1 ? "is a case" : "are cases"} of a suite that ran and passed, and gdUnit4 never ran ${casesNotRun.length === 1 ? "it" : "them"} and said nothing of it, so the run is not called passed, as gdharness test judges it.` : undefined,
53457
+ rowsMismatched.length > 0 ? `gdUnit4 skipped what rowsMismatched names because the row's data does not fit the case's parameters, though it printed ${rowsMismatched.length === 1 ? "the row" : "each row"} passed first; each entry carries gdUnit4's reason. The run is not called passed, as gdharness test judges it.` : undefined,
53342
53458
  notSuites.length > 0 ? "What notSuites names declares cases gdUnit4 never runs, so the run is not called passed, as gdharness test judges it." : undefined,
53343
53459
  briefed > 0 ? `${briefed} of the ${failed.length} failing cases, those with the largest reports, are named with their first message only, since their reports would have made the answer too large to deliver; pass one case's suite path to see its reports.` : undefined,
53344
53460
  notApplied.length > 0 ? "An ignore entry named a case that ran all the same: gdUnit4 leaves a case out only as suite_name:test_name, and reads an entry with no colon as a suite name or a piece of a suite path. ignoreNotApplied gives the form for each case that ran." : undefined,
53345
53461
  unmatched.length > 0 ? `${unmatched.join(", ")} ${unmatched.length === 1 ? "names" : "name"} no suite, case or row under the paths run, so ${unmatched.length === 1 ? "it" : "they"} left nothing out.` : undefined,
53346
53462
  uncaughtNote(uncaught),
53347
- undeclaredErrors.length > 0 ? `An engine error or warning a suite does not declare fails the run, as gdharness test judges it; ${declaringAdvice(undeclaredErrors[0]?.case ?? "test_name", undeclaredErrors[0]?.warning === true)}, once for each time it prints it${undeclaredErrors.some((error) => error.warning !== undeclaredErrors[0]?.warning) ? undeclaredErrors[0]?.warning === true ? ", and an error in its GDHARNESS_EXPECTED_ERRORS" : ", and a warning in its GDHARNESS_EXPECTED_WARNINGS" : ""}.` : undefined,
53463
+ undeclaredErrors.length > 0 ? `An engine error or warning a suite does not declare fails the run, as gdharness test judges it; ${declaringAdvice(undeclaredErrors[0]?.case ?? "test_name", undeclaredErrors[0]?.warning === true)}, once for each time it prints it${undeclaredErrors.some((error) => error.warning !== undeclaredErrors[0]?.warning) ? undeclaredErrors[0]?.warning === true ? ", and an error in its GDHARNESS_EXPECTED_ERRORS" : ", and a warning in its GDHARNESS_EXPECTED_WARNINGS" : ""}.${declaredAs}` : undefined,
53348
53464
  declaredNotPrinted.length > 0 ? "A declared error or warning the case did not print fails the run too, since the case no longer reaches what it was written to test." : undefined,
53349
53465
  walkStopped ? WALK_STOPPED_SENTENCE : undefined,
53350
53466
  countedLeaks.length > 0 ? [
@@ -53621,7 +53737,7 @@ ${error.text}`;
53621
53737
  return null;
53622
53738
  }
53623
53739
  try {
53624
- return readFileSync36(contained.absolutePath, "utf8");
53740
+ return readFileSync37(contained.absolutePath, "utf8");
53625
53741
  } catch {
53626
53742
  return null;
53627
53743
  }
@@ -53715,7 +53831,7 @@ ${error.text}`;
53715
53831
  return null;
53716
53832
  }
53717
53833
  try {
53718
- return readFileSync36(contained.absolutePath, "utf8");
53834
+ return readFileSync37(contained.absolutePath, "utf8");
53719
53835
  } catch {
53720
53836
  return null;
53721
53837
  }
@@ -54234,7 +54350,7 @@ ${error.text}`;
54234
54350
  return null;
54235
54351
  }
54236
54352
  try {
54237
- return settingKeys(readFileSync36(join43(projectPath, "project.godot"), "utf8"));
54353
+ return settingKeys(readFileSync37(join43(projectPath, "project.godot"), "utf8"));
54238
54354
  } catch {
54239
54355
  return null;
54240
54356
  }
@@ -55059,7 +55175,7 @@ ${error.text}`;
55059
55175
  return 0;
55060
55176
  }
55061
55177
  try {
55062
- return reportAlreadyInTranscript(readFileSync36(game.transcript), readFileSync36(report));
55178
+ return reportAlreadyInTranscript(readFileSync37(game.transcript), readFileSync37(report));
55063
55179
  } catch (error) {
55064
55180
  this.logDebug(`Could not compare a picked-up run's report with its transcript: ${errorMessage(error)}`);
55065
55181
  return 0;
@@ -56443,6 +56559,8 @@ ${error.text}`;
56443
56559
  let writtenBefore = null;
56444
56560
  let blind = { unseen: [] };
56445
56561
  let waitedForEditorMs = 0;
56562
+ let answerStalledMs = 0;
56563
+ const deadline = () => started + timeoutMs + answerStalledMs;
56446
56564
  let startUnanswered = false;
56447
56565
  let completedBefore;
56448
56566
  let uidsReread = [];
@@ -56450,7 +56568,15 @@ ${error.text}`;
56450
56568
  for (;; ) {
56451
56569
  const busySince = Date.now();
56452
56570
  let wasBusy = false;
56453
- 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
+ }
56454
56580
  wasBusy = true;
56455
56581
  await new Promise((settle) => setTimeout(settle, 100));
56456
56582
  }
@@ -56475,7 +56601,7 @@ ${error.text}`;
56475
56601
  if (duplicated !== undefined) {
56476
56602
  uidsDuplicatedOnDisk = duplicated.map((group) => Array.isArray(group) ? group.map(String) : []);
56477
56603
  }
56478
- if (firstAnswer["started"] !== false || Date.now() - started >= timeoutMs) {
56604
+ if (firstAnswer["started"] !== false || Date.now() >= deadline()) {
56479
56605
  break;
56480
56606
  }
56481
56607
  await new Promise((settle) => setTimeout(settle, 100));
@@ -56485,7 +56611,9 @@ ${error.text}`;
56485
56611
  const ghosts = blind.stillHeld ?? [];
56486
56612
  let busy = true;
56487
56613
  let idleSince = null;
56488
- while (Date.now() - started < timeoutMs) {
56614
+ let looked = false;
56615
+ while (!looked || Date.now() < deadline()) {
56616
+ looked = true;
56489
56617
  await new Promise((settle) => setTimeout(settle, 100));
56490
56618
  let status;
56491
56619
  try {
@@ -56531,6 +56659,7 @@ ${error.text}`;
56531
56659
  let reloaded = {};
56532
56660
  const reloadedFirst = [];
56533
56661
  let builtOn;
56662
+ let builtOnUnchecked = false;
56534
56663
  const reloadingSource = reloading === undefined || projectPath === "" ? null : this.sourceOfClass(projectPath, reloading);
56535
56664
  if (reloading !== undefined && reloadingSource !== null && isToolScript(reloadingSource)) {
56536
56665
  reloaded = {
@@ -56548,6 +56677,7 @@ ${error.text}`;
56548
56677
  answer = await reloadAsked();
56549
56678
  }
56550
56679
  builtOn = first.said;
56680
+ builtOnUnchecked = first.unchecked === true;
56551
56681
  }
56552
56682
  const methods = readArray(answer, "methods");
56553
56683
  const listed = (field) => {
@@ -56574,7 +56704,8 @@ ${error.text}`;
56574
56704
  names: projectPath === "" ? [] : this.classesAScriptNames(projectPath, script),
56575
56705
  restored: answer["restored"] === true,
56576
56706
  restoredAs: readString(answer, "restoredAs") ?? "",
56577
- ...builtOn === undefined ? {} : { builtOn }
56707
+ ...builtOn === undefined ? {} : { builtOn },
56708
+ ...builtOnUnchecked ? { builtOnUnchecked } : {}
56578
56709
  })
56579
56710
  };
56580
56711
  } else {
@@ -56602,6 +56733,9 @@ ${error.text}`;
56602
56733
  const dependents = busy || projectPath === "" || broughtIn.length === 0 ? null : await this.reloadDependents(projectPath, broughtIn);
56603
56734
  const stillGone = lost.filter((name) => !restored.includes(name));
56604
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
+ }
56605
56739
  if (busy) {
56606
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.");
56607
56741
  if (unseenBefore.length > 0) {
@@ -56630,6 +56764,7 @@ ${error.text}`;
56630
56764
  stillWorking: busy,
56631
56765
  waitedMs: Date.now() - started,
56632
56766
  waitedForEditorScanMs: waitedForEditorMs > 0 ? waitedForEditorMs : undefined,
56767
+ waitedForEditorAnswerMs: answerStalledMs > 0 ? answerStalledMs : undefined,
56633
56768
  reloadedAs: reloaded.as,
56634
56769
  ...reloadDifference(reloaded),
56635
56770
  ...args["listMembers"] === true ? {
@@ -56747,17 +56882,20 @@ ${error.text}`;
56747
56882
  try {
56748
56883
  const answer = asParams(await this.godotBridge.invokeTool("scripts_behind", { scriptPaths }));
56749
56884
  const behind = readArray(answer, "behind");
56750
- 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) };
56751
56886
  } catch (error) {
56752
- this.logDebug(`The editor would not say which scripts it holds behind their files: ${errorMessage(error)}`);
56753
- return null;
56887
+ return { unchecked: `the editor would not say: ${errorMessage(error)}` };
56754
56888
  }
56755
56889
  }
56756
56890
  async reloadWhatItIsBuiltOn(projectPath, scriptPath) {
56757
56891
  const against = this.scriptsCompiledAgainst(projectPath, scriptPath);
56758
56892
  const held = await this.scriptsHeldBehind(against);
56759
- if (held === null) {
56760
- 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
+ };
56761
56899
  }
56762
56900
  const reloadedFor = scriptPath.startsWith("res://") ? scriptPath : `res://${scriptPath.replace(/^\/+/, "")}`;
56763
56901
  const reloaded = [];
@@ -57407,10 +57545,11 @@ ${error.text}`;
57407
57545
  const checked = await this.classesTheEditorCannotSee(args);
57408
57546
  const stillHeld = checked.stillHeld ?? [];
57409
57547
  const projectPath = typeof args["projectPath"] === "string" ? args["projectPath"] : "";
57410
- const listed = projectPath === "" ? null : cachedClasses(projectPath);
57548
+ const listed = projectPath === "" || checked.unchecked !== undefined ? null : cachedClasses(projectPath);
57411
57549
  const held = listed === null ? null : await this.scriptsHeldBehind([...new Set(listed.values())]);
57412
- const behind = listed === null || held === null ? [] : [...listed].filter(([, path]) => held.behind.includes(path)).map(([className, scriptPath]) => ({ className, scriptPath }));
57413
- 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) {
57414
57553
  return answered;
57415
57554
  }
57416
57555
  const payload = asParams(JSON.parse(first.text));
@@ -57433,6 +57572,7 @@ ${error.text}`;
57433
57572
  stillHeldByEditor: stillHeld.length > 0 ? stillHeld : undefined,
57434
57573
  heldBehindInEditor: behind.length > 0 ? behind : undefined,
57435
57574
  classesUnchecked: checked.unchecked,
57575
+ heldBehindUnchecked: behindUnchecked,
57436
57576
  note: notes.length > 0 ? notes.join(" ") : undefined
57437
57577
  });
57438
57578
  }
@@ -57442,7 +57582,7 @@ ${error.text}`;
57442
57582
  }
57443
57583
  const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57444
57584
  const declared = new RegExp(`^\\s*(?:@\\w+(?:\\([^)]*\\))?\\s+)*class_name\\s+${escaped}\\b`, "m");
57445
- if (declared.test(readFileSync36(scriptPath, "utf8"))) {
57585
+ if (declared.test(readFileSync37(scriptPath, "utf8"))) {
57446
57586
  return null;
57447
57587
  }
57448
57588
  const checked = await this.classesTheEditorCannotSee(args);
@@ -57953,7 +58093,7 @@ async function runGodotServer() {
57953
58093
  const server = new GodotServer;
57954
58094
  await server.run();
57955
58095
  }
57956
- 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;
57957
58097
  var init_server = __esm(() => {
57958
58098
  init_dist2();
57959
58099
  init_stdio();
@@ -58126,7 +58266,7 @@ init_alive();
58126
58266
  init_engine_launch();
58127
58267
  init_lsp_client();
58128
58268
  init_process_children();
58129
- 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";
58130
58270
  import { connect as connect2 } from "node:net";
58131
58271
  import { join as join20, resolve as resolve7 } from "node:path";
58132
58272
  var EDITOR_RECORD_PROTOCOL = 1;
@@ -58145,7 +58285,7 @@ function editorRecordsIn(projectPath) {
58145
58285
  for (const name of names) {
58146
58286
  const file = join20(directory, name);
58147
58287
  try {
58148
- const said = JSON.parse(readFileSync15(file, "utf8"));
58288
+ const said = JSON.parse(readFileSync16(file, "utf8"));
58149
58289
  const { protocol, pid, lsp_port: lspPort, written_at_ms: writtenAtMs } = said;
58150
58290
  if (protocol === EDITOR_RECORD_PROTOCOL && typeof pid === "number" && typeof lspPort === "number" && lspPort > 0 && typeof writtenAtMs === "number") {
58151
58291
  records.push({ file, pid, lspPort, writtenAtMs });
@@ -58654,7 +58794,7 @@ init_engine_launch();
58654
58794
  init_engine_slots();
58655
58795
  init_setup();
58656
58796
  init_suite_judge();
58657
- 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";
58658
58798
  import { availableParallelism as availableParallelism3 } from "node:os";
58659
58799
  import { join as join23, resolve as resolve9 } from "node:path";
58660
58800
  import process13 from "node:process";
@@ -58842,17 +58982,17 @@ async function runSpreadCommand(options, godotPath) {
58842
58982
  const faults = left.flatMap((worker) => engineFaults(worker, options.godotVersion));
58843
58983
  const arms = left.map((_, index) => {
58844
58984
  const file = join23(shareDirectory, `worker-${String(index)}.arms`);
58845
- return existsSync20(file) ? armsPlayed(readFileSync18(file, "utf8")) : [];
58985
+ return existsSync20(file) ? armsPlayed(readFileSync19(file, "utf8")) : [];
58846
58986
  });
58847
58987
  faults.push(...shareFaults(arms));
58848
58988
  if (faults.length > 0) {
58849
58989
  return failed(faults, left, runDirectory, "so nothing was gathered");
58850
58990
  }
58851
58991
  const gather = await engine("gather", `${GATHER}${String(workers)}`);
58852
- console.log(readFileSync18(gather.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd());
58992
+ console.log(readFileSync19(gather.log, "utf8").replace(CONTROL_SEQUENCE, "").trimEnd());
58853
58993
  const gatherFaults = engineFaults(gather, options.godotVersion);
58854
58994
  const gatheredFile = join23(shareDirectory, "gathered.arms");
58855
- 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));
58856
58996
  const ungathered = (arms[0] ?? []).filter((arm) => !gathered.has(arm.arm)).map((arm) => arm.arm);
58857
58997
  if (ungathered.length > 0) {
58858
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`);
@@ -58906,7 +59046,7 @@ function engineFaults(engine, godotVersion) {
58906
59046
  if (!existsSync20(engine.log)) {
58907
59047
  return faults;
58908
59048
  }
58909
- const printed = readRun(readFileSync18(engine.log, "utf8"));
59049
+ const printed = readRun(readFileSync19(engine.log, "utf8"));
58910
59050
  const wrongEngine = godotVersion === null ? null : versionFault(printed.engineVersion, godotVersion);
58911
59051
  if (wrongEngine !== null) {
58912
59052
  faults.push(`${engine.name}: ${wrongEngine}`);
@@ -58919,7 +59059,7 @@ function engineFaults(engine, godotVersion) {
58919
59059
  }
58920
59060
  function progressOf(logs) {
58921
59061
  const each = logs.map((log, index) => {
58922
- const text = existsSync20(log) ? readFileSync18(log, "utf8") : "";
59062
+ const text = existsSync20(log) ? readFileSync19(log, "utf8") : "";
58923
59063
  const last = [...text.matchAll(PROGRESS)].at(-1);
58924
59064
  return last === undefined ? `worker ${String(index)} has played no arm yet` : `worker ${String(index)} at ${last[1] ?? ""} ${last[2] ?? ""} of ${last[3] ?? ""}`;
58925
59065
  });
@@ -58951,7 +59091,7 @@ import {
58951
59091
  existsSync as existsSync23,
58952
59092
  mkdirSync as mkdirSync10,
58953
59093
  readdirSync as readdirSync14,
58954
- readFileSync as readFileSync22,
59094
+ readFileSync as readFileSync23,
58955
59095
  rmSync as rmSync12,
58956
59096
  statSync as statSync14,
58957
59097
  writeFileSync as writeFileSync12
@@ -58963,7 +59103,7 @@ import process14 from "node:process";
58963
59103
  // src/suite-fingerprint.ts
58964
59104
  init_class_cache();
58965
59105
  import { createHash as createHash6 } from "node:crypto";
58966
- 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";
58967
59107
  import { join as join24, posix as posix2 } from "node:path";
58968
59108
  var RES_LITERAL = /res:\/\/[^"'\s)\]<>]*/g;
58969
59109
  var RELATIVE_LOAD = /\b(?:pre)?load\(\s*["']((?!res:\/\/|uid:\/\/)[^"']+)["']/g;
@@ -59028,7 +59168,7 @@ ${this.read(path).digest}
59028
59168
  walk(file, `${under}${entry.name}/`);
59029
59169
  } else if (entry.isFile()) {
59030
59170
  hash.update(`${under}${entry.name}
59031
- `).update(readFileSync19(file));
59171
+ `).update(readFileSync20(file));
59032
59172
  }
59033
59173
  }
59034
59174
  };
@@ -59054,7 +59194,7 @@ ${this.read(path).digest}
59054
59194
  if (BINARY_RESOURCE.test(path)) {
59055
59195
  return { digest: "", reaches: [], unreadable: `${path} is a binary resource` };
59056
59196
  }
59057
- const bytes = readFileSync19(file);
59197
+ const bytes = readFileSync20(file);
59058
59198
  const digest = createHash6("sha256").update(bytes).digest("hex");
59059
59199
  const text = bytes.toString("latin1");
59060
59200
  const reaches = new Set;
@@ -59092,7 +59232,7 @@ ${this.read(path).digest}
59092
59232
  }
59093
59233
  function extensionsOf(projectPath) {
59094
59234
  const list = join24(projectPath, ".godot", "extension_list.cfg");
59095
- return existsSync21(list) ? readFileSync19(list, "utf8").split(`
59235
+ return existsSync21(list) ? readFileSync20(list, "utf8").split(`
59096
59236
  `).map((line) => line.trim()).filter((line) => line.startsWith("res://")) : [];
59097
59237
  }
59098
59238
 
@@ -59101,7 +59241,7 @@ init_suite_judge();
59101
59241
  init_suite_plan();
59102
59242
 
59103
59243
  // src/version.ts
59104
- import { readFileSync as readFileSync21 } from "node:fs";
59244
+ import { readFileSync as readFileSync22 } from "node:fs";
59105
59245
  import { dirname as dirname12, join as join26 } from "node:path";
59106
59246
  import { fileURLToPath as fileURLToPath3 } from "node:url";
59107
59247
  function getLocalVersion() {
@@ -59112,7 +59252,7 @@ function getLocalVersion() {
59112
59252
  ];
59113
59253
  for (const packagePath of packageCandidates) {
59114
59254
  try {
59115
- const value = JSON.parse(readFileSync21(packagePath, "utf-8"));
59255
+ const value = JSON.parse(readFileSync22(packagePath, "utf-8"));
59116
59256
  if (typeof value === "object" && value !== null && "name" in value && value.name === "gdharness" && "version" in value && typeof value.version === "string") {
59117
59257
  return value.version;
59118
59258
  }
@@ -59373,9 +59513,9 @@ async function runTestCommand(options, godotPath) {
59373
59513
  }
59374
59514
  const fingerprints = new SuiteFingerprints(scripts, [
59375
59515
  getLocalVersion(),
59376
- readFileSync22(shippedListedRunner(), "utf8"),
59516
+ readFileSync23(shippedListedRunner(), "utf8"),
59377
59517
  ...options.reachAlso.map((path) => `${path}
59378
- ${existsSync23(path) ? readFileSync22(path, "latin1") : "missing"}`)
59518
+ ${existsSync23(path) ? readFileSync23(path, "latin1") : "missing"}`)
59379
59519
  ]);
59380
59520
  const printsOf = new Map(suites.map((suite) => [suite.path, fingerprints.of(suite)]));
59381
59521
  if (options.changedOnly) {
@@ -59491,7 +59631,7 @@ function roomSaid(engines, lanes, unheld, held, asked) {
59491
59631
  }
59492
59632
  function readRecord(file) {
59493
59633
  try {
59494
- const parsed = JSON.parse(readFileSync22(file, "utf8"));
59634
+ const parsed = JSON.parse(readFileSync23(file, "utf8"));
59495
59635
  return typeof parsed === "object" && parsed !== null ? parsed : {};
59496
59636
  } catch {
59497
59637
  return {};
@@ -59550,7 +59690,7 @@ async function importProject(launch) {
59550
59690
  if (ended.ended !== null || ended.code !== 0) {
59551
59691
  return `${ended.ended ?? `the engine exited ${exitNamed(ended.code ?? 0)}`}; everything it printed is in ${launch.logs.named(log)}`;
59552
59692
  }
59553
- const printed = readRun(readFileSync22(log, "utf8"));
59693
+ const printed = readRun(readFileSync23(log, "utf8"));
59554
59694
  const wrongEngine = launch.godotVersion === null ? null : versionFault(printed.engineVersion, launch.godotVersion);
59555
59695
  if (wrongEngine !== null) {
59556
59696
  return `${wrongEngine}; everything it printed is in ${launch.logs.named(log)}`;
@@ -59666,7 +59806,7 @@ async function runEngine2(launch, plan, index, times, again = "", slot = null) {
59666
59806
  launch.slots.release(lock);
59667
59807
  }
59668
59808
  }
59669
- const printed = readRun(readFileSync22(log, "utf8"));
59809
+ const printed = readRun(readFileSync23(log, "utf8"));
59670
59810
  const assignment = {
59671
59811
  suites: plan.suites,
59672
59812
  expected: readExpected(expectedFile),
@@ -59701,7 +59841,7 @@ function report(ran, across, uncollected, startedAt, logs, printOutput, targets)
59701
59841
  for (const engine of ran) {
59702
59842
  console.log("");
59703
59843
  console.log(`--- what engine ${String(engine.index)} printed, running ${named(engine.plan.suites.map((suite) => suite.path))}:`);
59704
- 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)");
59705
59845
  }
59706
59846
  }
59707
59847
  const failed = ran.filter((engine) => !engine.verdict.passed);
@@ -59720,7 +59860,7 @@ function report(ran, across, uncollected, startedAt, logs, printOutput, targets)
59720
59860
  writeFileSync12(engine.log, `${verdict.join(`
59721
59861
  `)}
59722
59862
  ---
59723
- ${readFileSync22(engine.log, "utf8")}`);
59863
+ ${readFileSync23(engine.log, "utf8")}`);
59724
59864
  }
59725
59865
  if (engine.verdict.shown.length > 0) {
59726
59866
  console.log("");