gdharness 1.3.35 → 1.3.36

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 (30) hide show
  1. package/build/cli.js +226 -57
  2. package/build/godot/addons/gdharness_editor/animation_reads.gd +17 -1
  3. package/build/godot/addons/gdharness_editor/reading.gd +10 -2
  4. package/build/godot/addons/gdharness_editor/scene_file.gd +12 -6
  5. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +57 -0
  6. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +16 -0
  7. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +81 -4
  8. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +3 -3
  9. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -2
  10. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +59 -0
  11. package/build/godot/addons/gdharness_editor/tools/script_paths.gd +148 -0
  12. package/build/godot/addons/gdharness_editor/tools/script_paths.gd.uid +1 -0
  13. package/build/godot/addons/gdharness_runtime/reading.gd +10 -2
  14. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +70 -0
  15. package/build/godot/addons/gdharness_runtime/runtime_input.gd +223 -248
  16. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +128 -5
  17. package/build/godot/addons/gdharness_runtime/runtime_screen.gd +107 -0
  18. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +15 -1
  19. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +8 -0
  20. package/build/godot/addons/gdharness_runtime/runtime_words.gd +22 -0
  21. package/build/godot/operations/animation_reads.gd +17 -1
  22. package/build/godot/operations/classdb_queries.gd +16 -0
  23. package/build/godot/operations/dependencies.gd +46 -4
  24. package/build/godot/operations/editor_tools.gd +5 -5
  25. package/build/godot/operations/gdscript_authoring.gd +7 -3
  26. package/build/godot/operations/input_actions.gd +105 -0
  27. package/build/godot/operations/project_diagnostics.gd +4 -0
  28. package/build/godot/operations/project_validation.gd +15 -0
  29. package/build/godot/operations/reading.gd +10 -2
  30. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -8159,7 +8159,8 @@ function resolveWithinProject(projectPath, candidatePath, options = {}) {
8159
8159
  }
8160
8160
  if (withoutScheme.startsWith("/") || withoutScheme.startsWith("\\") || isAbsolute(withoutScheme)) {
8161
8161
  const tail = withoutScheme.split(/[\\/]+/).filter((step) => step !== "" && !step.endsWith(":"));
8162
- const example = tail.slice(-2).join("/") || "scenes/main.tscn";
8162
+ const besideIt = isAbsolute(trimmed) && isWithinRoot(realAsFarAsItExists(dirname2(resolve(trimmed))), realAsFarAsItExists(root));
8163
+ const example = tail.slice(besideIt ? -1 : -2).join("/") || "scenes/main.tscn";
8163
8164
  return {
8164
8165
  ok: false,
8165
8166
  reason: `Path '${trimmed}' is absolute and outside the project; give a path inside it, such as '${example}'.`
@@ -11806,8 +11807,11 @@ function holderOf(content) {
11806
11807
  if (typeof parsed !== "object" || parsed === null) {
11807
11808
  return null;
11808
11809
  }
11809
- const { pid, started } = parsed;
11810
- return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && typeof started === "number" ? { pid, started } : null;
11810
+ const { pid, started, by } = parsed;
11811
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof started !== "number") {
11812
+ return null;
11813
+ }
11814
+ return typeof by === "string" && by !== "" ? { pid, started, by } : { pid, started };
11811
11815
  } catch {
11812
11816
  return null;
11813
11817
  }
@@ -11865,7 +11869,7 @@ class EngineSlots {
11865
11869
  }
11866
11870
  } else if (this.say !== undefined && this.held.size === 0 && now - began >= COMMAND_WAIT_SAID_MS && now - this.saidAt >= WAIT_SAID_MS) {
11867
11871
  this.saidAt = now;
11868
- this.say(slotWaitSaid(now - began, this.count, this.directory));
11872
+ this.say(slotWaitSaid(now - began, this.count, this.directory, this.holders()));
11869
11873
  }
11870
11874
  if (changes === undefined) {
11871
11875
  changes = new FolderChanges(this.directory);
@@ -11906,6 +11910,18 @@ class EngineSlots {
11906
11910
  this.marks.add(mark);
11907
11911
  return mark;
11908
11912
  }
11913
+ holders() {
11914
+ const found = [];
11915
+ for (let slot = 0;slot < this.count; slot += 1) {
11916
+ try {
11917
+ const holder = holderOf(readFileSync7(join8(this.directory, `${String(slot)}.lock`), "utf8"));
11918
+ if (holder !== null) {
11919
+ found.push(holder);
11920
+ }
11921
+ } catch {}
11922
+ }
11923
+ return found;
11924
+ }
11909
11925
  releaseAll() {
11910
11926
  for (const lock of this.held) {
11911
11927
  this.release(lock);
@@ -11994,21 +12010,42 @@ function sayWaitsTo(say) {
11994
12010
  function processSlots() {
11995
12011
  ours ??= ownStart().then((started) => {
11996
12012
  const chosen = machineSlots();
11997
- return new EngineSlots(chosen.directory, chosen.count, { pid: process7.pid, started }, (line) => {
12013
+ const holder = { pid: process7.pid, started, ...takenBy === undefined ? {} : { by: takenBy } };
12014
+ return new EngineSlots(chosen.directory, chosen.count, holder, (line) => {
11998
12015
  saidTo?.(line);
11999
12016
  });
12000
12017
  });
12001
12018
  return ours;
12002
12019
  }
12003
- function slotWaitSaid(waitedMs, count, directory) {
12020
+ function nameSlotTaker(by) {
12021
+ takenBy = by;
12022
+ }
12023
+ function slotWaitSaid(waitedMs, count, directory, holders = []) {
12004
12024
  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`;
12005
- return `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine: ${slots}, by a gate or another tool, and the engine starts when one is given back.`;
12025
+ const held = new Map;
12026
+ for (const holder of holders) {
12027
+ const known = held.get(holder.pid);
12028
+ held.set(holder.pid, { by: known?.by ?? holder.by, slots: (known?.slots ?? 0) + 1 });
12029
+ }
12030
+ const named = [...held].map(([pid, { by, slots: taken }]) => {
12031
+ const who = pid === process7.pid ? `this process (pid ${String(pid)})` : by === undefined ? `pid ${String(pid)}` : `${by} (pid ${String(pid)})`;
12032
+ return count === 1 ? who : `${String(taken)} by ${who}`;
12033
+ });
12034
+ const waiting = `Waiting${waitedMs >= 1000 ? ` ${String(Math.round(waitedMs / 1000))} s so far` : ""} for an engine: ${slots}`;
12035
+ if (named.length === 0) {
12036
+ return `${waiting}, by a gate or another tool, and the engine starts when one is given back.`;
12037
+ }
12038
+ if (count === 1) {
12039
+ return `${waiting}, by ${named.join(", ")}, and the engine starts when it is given back.`;
12040
+ }
12041
+ const list = named.length === 1 ? named.join("") : `${named.slice(0, -1).join(", ")} and ${named.at(-1) ?? ""}`;
12042
+ return `${waiting}: ${list}. The engine starts when one is given back.`;
12006
12043
  }
12007
12044
  async function slotForCall() {
12008
12045
  const slots = await processSlots();
12009
12046
  const progress = callProgress();
12010
12047
  const lock = await slots.take(callSignal(), progress === undefined ? undefined : (waitedMs) => {
12011
- progress(waitedMs / 1000, slotWaitSaid(waitedMs, slots.count, slots.directory));
12048
+ progress(waitedMs / 1000, slotWaitSaid(waitedMs, slots.count, slots.directory, slots.holders()));
12012
12049
  });
12013
12050
  return {
12014
12051
  release: () => {
@@ -12024,7 +12061,7 @@ async function inASlot(body) {
12024
12061
  held.release();
12025
12062
  }
12026
12063
  }
12027
- var SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 5, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", waitsMarked = 0, ours, saidTo;
12064
+ var SLOTS_VARIABLE = "GODOT_ENGINE_SLOTS", SLOT_COUNT_VARIABLE = "GODOT_ENGINE_SLOT_COUNT", PROCESSORS_A_SLOT = 5, START_TOLERANCE_MS = 2000, UNWRITTEN_MS = 1e4, SLOT_LOOK_MS = 1000, SLOT_SETTLE_MS = 50, WAIT_SAID_MS = 20000, COMMAND_WAIT_SAID_MS = 2000, START_READ_MS = 1e4, WAITING_FOLDER = "waiting", waitsMarked = 0, ours, saidTo, takenBy;
12028
12065
  var init_engine_slots = __esm(() => {
12029
12066
  init_alive();
12030
12067
  init_call_signal();
@@ -12831,6 +12868,39 @@ function projectFiles(projectPath, extensions) {
12831
12868
  function folderFiles(directory, extensions) {
12832
12869
  return gdignored(directory) ? [] : filesBelow(directory, extensions);
12833
12870
  }
12871
+ function underGdignore(projectPath, relative) {
12872
+ const folders = relative.split("/").slice(0, -1);
12873
+ for (let depth = 1;depth <= folders.length; depth += 1) {
12874
+ if (gdignored(join11(projectPath, ...folders.slice(0, depth)))) {
12875
+ return true;
12876
+ }
12877
+ }
12878
+ return false;
12879
+ }
12880
+ function steppedOverIn(directory) {
12881
+ if (gdignored(directory)) {
12882
+ return [{ folder: "", why: "gdignore" }];
12883
+ }
12884
+ const found = [];
12885
+ const visit = (at, prefix) => {
12886
+ for (const entry of readdirSync6(at, { withFileTypes: true })) {
12887
+ if (!entry.isDirectory() || skipped(entry.name)) {
12888
+ continue;
12889
+ }
12890
+ const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
12891
+ const path = join11(at, entry.name);
12892
+ if (gdignored(path)) {
12893
+ found.push({ folder: spelled, why: "gdignore" });
12894
+ } else if (installedByUs(path)) {
12895
+ found.push({ folder: spelled, why: "gdharness" });
12896
+ } else {
12897
+ visit(path, spelled);
12898
+ }
12899
+ }
12900
+ };
12901
+ visit(directory, "");
12902
+ return found.sort((a, b) => a.folder.localeCompare(b.folder));
12903
+ }
12834
12904
  function filesBelow(root, extensions) {
12835
12905
  const found = [];
12836
12906
  const visit = (directory, prefix) => {
@@ -16148,7 +16218,7 @@ function annotationsOf(spec) {
16148
16218
  openWorldHint: false
16149
16219
  };
16150
16220
  }
16151
- var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. Each file the written one refers to is named by its uid, as the editor names it, except a file the project's uid cache has no uid for yet (one made since the project was last scanned), which is named by path and listed under namedByPath with the call that scans it. An editor of the project that is running and has not reached this server is waited for rather than written around.", ALSO_SAVED = "A scene is saved as the engine holds it once the file has loaded, and one property can set others as it loads, as anchors_preset gives a centred Control both its grow directions, so a scene not last saved by an editor can gain, change or lose lines on nodes the call did not name: alsoSaved lists each as {node, property, was, saved}, was left out where the file set nothing and saved where it now sets nothing. The scene loads as it did; only its text changed.", IMPORTED_FIRST = "importedFirst names the files whose import was behind them on disk, such as a picture changed since it was imported or one never imported, which were imported before the run through project_import reimport, so the run reads them as they are now; it is left out when every import was current. notImportedFirst lists, as project_import reimport does, any it could not import, and importNote says that the run reads those as they were. The import gives every script without a .uid sidecar one, as the editor's scan does, writing it beside the script in the project, and uidsWrittenFirst names those scripts. The files inside an addon are counted per addon rather than named, under importedInAddons and uidsWrittenInAddons. importTookMs is how long the import took; it is outside the run's own time limit, so a call that imported can take that much longer than the limit it was given.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', EXPORT_PLATFORMS, RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR2, TOOL_SPECS, SPECS_BY_NAME;
16221
+ var PROJECT_INFO_SECTIONS, PROJECT_PATH, RUNNING_PROJECT_PATH, RUNNING_PID, SCENE_PATH, WITHOUT_AN_EDITOR = "Through the editor when one serves the project; with none, a headless engine runs the editor's own tool and the answer says so under writtenBy, or readBy for a read. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before anything is saved, and a file where that cannot be done, such as a scene whose script does not compile, is refused and nothing is written. Each file the written one refers to is named by its uid, as the editor names it, except a file the project's uid cache has no uid for yet (one made since the project was last scanned), which is named by path and listed under namedByPath with the call that scans it. An editor of the project that is running and has not reached this server is waited for rather than written around.", ALSO_SAVED = "A scene is saved as the engine holds it once the file has loaded, and one property can set others as it loads, as anchors_preset gives a centred Control both its grow directions, so a scene not last saved by an editor can gain, change or lose lines the call did not ask for, on any node the file held, the one it names among them: alsoSaved lists each as {node, property, was, saved}, was left out where the file set nothing and saved where it now sets nothing. What a set itself moved on its node is under alsoChanged rather than here, and a node the call adds, saved as the engine saves any new one, is not listed. The scene loads as it did; only its text changed.", IMPORTED_FIRST = "importedFirst names the files whose import was behind them on disk, such as a picture changed since it was imported or one never imported, which were imported before the run through project_import reimport, so the run reads them as they are now; it is left out when every import was current. notImportedFirst lists, as project_import reimport does, any it could not import, and importNote says that the run reads those as they were. The import gives every script without a .uid sidecar one, as the editor's scan does, writing it beside the script in the project, and uidsWrittenFirst names those scripts. The files inside an addon are counted per addon rather than named, under importedInAddons and uidsWrittenInAddons. importTookMs is how long the import took; it is outside the run's own time limit, so a call that imported can take that much longer than the limit it was given.", WHOLE_NUMBERS = 'A whole number of 2^53 or more, such as a 64-bit seed or a resource uid, is written as its digits, {"_type": "int", "value": "9007199254740993"}, the form a read answers with: a JSON number that large can arrive as a neighbour of the one sent, so sent as a number it is refused.', SCRIPT_PATH, RESOURCE_FILE = 'File inside the project, such as "sprites/hero.png" or "materials/steel.tres".', EXPORT_PLATFORMS, RESOURCE_PATH, NODE_PATH, PROPERTIES, XY, ANIMATION_TRACK, SCRIPT_MODIFICATIONS, INPUT_EVENTS, TILESET_SOURCES, TILEMAP_CELLS, COLOUR2, TOOL_SPECS, SPECS_BY_NAME;
16152
16222
  var init_tool_definitions = __esm(() => {
16153
16223
  init_launch();
16154
16224
  PROJECT_INFO_SECTIONS = [
@@ -16360,7 +16430,7 @@ var init_tool_definitions = __esm(() => {
16360
16430
  include: {
16361
16431
  type: "array",
16362
16432
  items: { type: "string", enum: [...PROJECT_INFO_SECTIONS] },
16363
- description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
16433
+ description: "Extra sections: registered autoloads, the classes the project's scripts declare with class_name, each with its script, read from the files, the input actions project.godot holds with their events (an engine ui_ action only when the project rebinds it, as engineAction), addons and whether each is enabled, export presets, the audio bus layout, a health report, or export validation. health and validation compile every script of the project in headless engines and name each that fails with its first error, each file a scene, resource or script refers to that does not exist, each input action a script reads by a name written in the call that neither the project nor the engine defines, with every line reading it, and each signal connection in a scene whose method or signal the node at its end does not have, judged from the node's type and scripts as their files are written."
16364
16434
  },
16365
16435
  preset: { type: "string", description: "For validation: the export preset to validate against." },
16366
16436
  detail: {
@@ -16737,7 +16807,7 @@ var init_tool_definitions = __esm(() => {
16737
16807
  title: "Run tests",
16738
16808
  effect: "additive",
16739
16809
  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. 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.",
16740
- answer: "passed is the verdict to read. exitCode is the engine's own exit, gdUnit4's code for what it saw, 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. 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. 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,
16810
+ 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. 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,
16741
16811
  parameters: {
16742
16812
  projectPath: PROJECT_PATH,
16743
16813
  path: {
@@ -16840,7 +16910,7 @@ var init_tool_definitions = __esm(() => {
16840
16910
  name: "scene_node",
16841
16911
  title: "Scene nodes",
16842
16912
  description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, paint TileMap cells, or bake a navigation region. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, and answers nodePath as its new path. A rename or a reparent carries the node paths naming the node, as the editor's rename does: NodePath values on nodes (in arrays, dictionaries, built-in resources, %unique paths) and the tracks its players' animations hold, saving a library kept in a .tres of its own, and answers each rewrite under retargeted and each path it left under notRetargeted. The move is followed into every other .tscn that instances or inherits this scene at any depth: the editable children, values, added nodes and connections named by the old path are rewritten, since Godot finds a moved node's values by unique_id but editable children only by path and drops the values kept below them on the next save, and that scene's node paths and tracks are carried as above. A delete rewrites no path, and answers under stillNaming what named the deleted node and now names nothing. A property holding a Resource takes the res:// path of one, so a texture, a material or a theme is a set like any other. A write is refused while the editor holds unsaved changes to the scene. A node this scene inherits takes values of this scene's own, and so does a node inside an instance of another scene once that instance's editableChildren is true; neither can be moved, renamed, copied or deleted, since the other scene makes it again on load. ${WITHOUT_AN_EDITOR}`,
16843
- answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}; namesNoNode, each node path written naming no node. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
16913
+ answer: "get answers nodeType, the node's engine class, with scriptClass when its script declares a class_name; nodePath, its path from the root however it was named; setHere, the properties this scene's own file sets on it; and valuesFrom, the scene every other value comes from, when another scene makes the node. A set that gives a property the value the node has without it (what the scene it comes from gives, or its default) leaves it out of the file, as the editor does, and answers it under notStored, with notStoredNote saying what it follows now. retargeted lists each path a rename or a reparent rewrote, as {scenePath, nodePath, property or animation and track, from, to}, and notRetargeted each it left, with a reason: a track kept in another scene or an imported file, or a value another scene gives that the move left naming nothing. followed names the other scenes a move rewrote, and notFollowed any left as they were, with why. scriptsNaming lists each line of a script that reads a node a rename, a reparent or a delete took by the path it had, as {scenePath, nodePath, script, line, from, to}: the script is on the node at nodePath, or is one that node's script extends, in this scene or one instancing or inheriting it, and to is the path that reaches the node from there now, absent after a delete. It reads $Path, %Unique, and a string handed to get_node, get_node_or_null or has_node on the node itself; a path built at run time is not seen. Scripts are left as they were, as the editor's own rename leaves them, so each line listed fails until it is changed. A 2D bake_navigation that read no obstacle says in its note that all its outlines enclose is walkable, and names the nodes giving obstacles that sit under no region. stillNaming lists each node path and track, here and in the scenes instancing or inheriting this one, that named a deleted node, and deletedWithIt each node of this scene's own that went with it, the nodes inside an instance going with the instance. groups lists, sorted, every group a node is in, on a get, an add that gave addGroups and every set that changes them; get adds groupsHere, the groups this file puts it in, when another scene puts it in others, and layers, each layer mask it answers (collision_layer, collision_mask, visibility_layer) as the layers it holds, {layer, name} with the name project.godot gives the layer. Each of these lists is answered only when it names anything. add and set answer each property given under properties as the node holds it once written; alsoChanged gives the other properties the engine changed in step with a set, as a Control's offsets with its size, and those a reparent changed to keep the node where it is drawn, as {was, now}; namesNoNode, each node path written naming no node. get adds placement, a Node3D's position, rotation_degrees and scale from its transform. " + ALSO_SAVED,
16844
16914
  parameters: {
16845
16915
  projectPath: PROJECT_PATH,
16846
16916
  scenePath: SCENE_PATH,
@@ -17046,6 +17116,12 @@ var init_tool_definitions = __esm(() => {
17046
17116
  description: "create and set: keyframe snap in seconds, 0 or more. create: default 0.1."
17047
17117
  },
17048
17118
  track: { ...ANIMATION_TRACK, ops: ["add_track", "remove_track"] },
17119
+ times: {
17120
+ type: "array",
17121
+ items: { type: "number" },
17122
+ ops: ["remove_track"],
17123
+ description: "remove_track: the times in seconds of the keys to remove, keeping the rest of the track; a track left with no key goes too. A time no key is at refuses the call, naming the times the keys are at. To move a key, remove it and add_track it at the new time. Default every key."
17124
+ },
17049
17125
  replaceKeys: {
17050
17126
  type: "boolean",
17051
17127
  ops: ["add_track"],
@@ -17161,7 +17237,7 @@ var init_tool_definitions = __esm(() => {
17161
17237
  {
17162
17238
  name: "script_edit",
17163
17239
  title: "Edit scripts",
17164
- description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read, and a script declaring a class_name is put in the project's class list, with registered saying whether it is there; a class_name another script or an engine class holds, or an extends naming no class, is refused with nothing written. modify makes every addition or none, refuses with nothing changed additions that would stop a script that parses from parsing, giving the engine's reasons, and answers with the line each one is on in the file written; with an editor serving the project it reloads the script there too, so the editor's answers read what was written, and editorReloaded says so (editorNote when it could not, or when a script tab holds unsaved edits that saving would write over the file). rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, of which the newest ten are kept, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
17240
+ description: "Creates a GDScript file, or adds functions, variables and signals to one. Every declaration written carries a type. Both load what they wrote under the project's own warning settings and answer with parses; the engine's reasons for a refusal come back under engine_messages. create answers with the extends and class_name the engine read (class_name null for a script declaring none), and a script declaring a class_name is put in the project's class list, with registered saying whether it is there, and an editor serving the project scans for it, so it resolves the class at once (editorRescanned); a class_name another script or an engine class holds, or an extends naming no class, is refused with nothing written. modify makes every addition or none, refuses with nothing changed additions that would stop a script that parses from parsing, giving the engine's reasons, and answers with the line each one is on in the file written; with an editor serving the project it reloads the script there too, so the editor's answers read what was written, and editorReloaded says so (editorNote when it could not, or when a script tab holds unsaved edits that saving would write over the file). rename renames a class or a member wherever the engine uses it and writes every file or none. A new name already taken is refused with nothing written, a member's included when a class inheriting it already uses the name in code, as a local or a parameter. changed lists each file with its changed lines as they now read, and leftAlone lists every other whole-word occurrence of the old name with what the tokenizer makes of it (code, string, comment, doc, nodePath, scene, text, or ignored for a file under a .gdignore directory, which is never changed) and, where it is not plain, why: a string that is the old name whole, such as find_children's type or a call's method name, which nothing renames or compiles and which is listed first; a node named like the class; a call on a value whose type the analyser could not tell. A member rename answers under languageServer how many requests it made and how long they took. A comment, documentation line, string or text file line with nothing more to say is given under leftAloneInProse by file, kind and line instead, and code that is certainly another symbol of the same name, a bare name in a file where nothing inherits the member or one the language server resolved to another declaration, under leftAloneOtherSymbols as each why with the files and lines it covers. summary.leftAlone counts each of the three lists beside the total. When those two grouped lists run past 300 lines, they are written with each line's text to a file under the project's .godot directory instead, of which the newest ten are kept, and leftAloneDetail names the file and counts what is in it. Read both before calling the rename done; nothing in them was changed. Afterwards an open editor scans the changed files and reloads every changed script, which is also the compile check, and its answer comes back under editor; with no editor the class cache is rebuilt and the changed scripts are compiled in a fresh engine, under classCache and compiled. written is true once the files are written, and ok is false when what came after the write failed: the files are left changed, and note says so.",
17165
17241
  parameters: {
17166
17242
  projectPath: PROJECT_PATH,
17167
17243
  scriptPath: SCRIPT_PATH,
@@ -17265,13 +17341,13 @@ var init_tool_definitions = __esm(() => {
17265
17341
  name: "script_diagnostics",
17266
17342
  title: "Script diagnostics",
17267
17343
  effect: "read",
17268
- 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: 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 warnings null, since the engine reports warnings only to an editor. 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.",
17344
+ 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 warnings null, since the engine reports warnings only to an editor. 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.",
17269
17345
  parameters: {
17270
17346
  projectPath: PROJECT_PATH,
17271
17347
  scriptPath: {
17272
17348
  type: ["string", "array"],
17273
17349
  items: { type: "string" },
17274
- description: 'A script inside the project, a folder, or a list of either, such as "scripts/player.gd" or ["scripts/economy", "ui/shop.gd"]. A folder is its .gd files at any depth, skipping what the engine skips. One script is answered on its own. Several are answered together, with clean, errors and warnings for all of them: a script with nothing to report is named in nothingReported, and every other is answered under scripts in that same shape, including a script that could not be read, named with why while the rest are still asked about. Any number of scripts is one call, which reports progress as it goes.'
17350
+ description: 'A script inside the project, a folder, or a list of either, such as "scripts/player.gd" or ["scripts/economy", "ui/shop.gd"]. A folder is its .gd files at any depth, skipping what the engine skips and the addons gdharness installed; a folder whose scripts all sit in those is refused, naming each. A script named that sits under a folder holding a .gdignore is checked as asked and listed under underGdignore, since the engine never loads it and its errors do not stop the project. One script is answered on its own. Several are answered together, with clean, errors and warnings for all of them: a script with nothing to report is named in nothingReported, and every other is answered under scripts in that same shape, including a script that could not be read, named with why while the rest are still asked about. Any number of scripts is one call, which reports progress as it goes.'
17275
17351
  }
17276
17352
  },
17277
17353
  requires: ["projectPath", "scriptPath"]
@@ -17279,7 +17355,7 @@ var init_tool_definitions = __esm(() => {
17279
17355
  {
17280
17356
  name: "resource_edit",
17281
17357
  title: "Edit resources",
17282
- description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, with resourceType the engine class and scriptClass the class_name of its script when it declares one, and a Theme lists its items there as "Button/colors/font_color". A Curve, Curve2D or Curve3D reads as the inspector shows it, point_count and point_0/position and the rest, rather than the _data list the file stores, and so does one built into a resource or a scene. create and modify answer each property given under properties as the file holds it once saved, in the same form. A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. A property holding a resource takes its res:// path; a file never imported is refused with the reimport that imports it. move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before the save, and a resource where that cannot be done is refused and the file left as it was.',
17358
+ description: 'Resource files: read one, create any ClassDB resource as .tres, change one, write a shader, build a TileSet, or set a Theme colour or font size. A material is a create with resourceType StandardMaterial3D, ShaderMaterial or CanvasItemMaterial. read answers with the properties the file sets, in the form modify takes, with resourceType the engine class and scriptClass the class_name of its script when it declares one, and a Theme lists its items there as "Button/colors/font_color", so a style box, a font or a constant is set through modify by that name, as "Button/styles/normal" given the res:// path of a StyleBox. A Curve, Curve2D or Curve3D reads as the inspector shows it, point_count and point_0/position and the rest, rather than the _data list the file stores, and so does one built into a resource or a scene. create and modify answer each property given under properties as the file holds it once saved, in the same form. A create or modify whose properties made the engine change others, as a Gradient given more colours than offsets gives each new point an offset of 0, names each of those under alsoChanged with what it held before and holds now. A property holding a resource takes its res:// path; a file never imported is refused with the reimport that imports it. move takes any file of the project, a script or an image as much as a resource, or a directory, and works from the files, so it needs no editor; one serving the project is brought up to it as after a move in its own FileSystem dock, its open scenes and cached resources named for the new paths and the project settings the move rewrote taken from the file. read needs no editor either: with none serving the project, a headless engine reads the file. The ops that write a resource go through the editor when one serves the project; with none, a headless engine runs the tool the editor would and the answer says so under writtenBy. That engine runs the script of the resource, where the editor runs only tool scripts, so what a script changes as the file loads or as a property is set is put back before the save, and a resource where that cannot be done is refused and the file left as it was.',
17283
17359
  parameters: {
17284
17360
  projectPath: PROJECT_PATH,
17285
17361
  resourcePath: {
@@ -17403,7 +17479,7 @@ var init_tool_definitions = __esm(() => {
17403
17479
  silent: {
17404
17480
  type: "boolean",
17405
17481
  ops: ["open", "restart"],
17406
- description: "Open the editor with no sound out of the machine, started with --audio-driver Dummy, so a tool script, a preview or a scene playing in its viewport is heard by nobody. Default true on open, and on restart how the editor being replaced was started: pass false to hear it. The answer's arguments are what the editor was started with. The editor hands no audio driver to a game it plays, so editor_run's own silent decides that game's sound. A hidden editor is headless and silent either way. On restart only for an editor a server opened; one opened by hand comes back as it was."
17482
+ description: "Open the editor with no sound out of the machine, started with --audio-driver Dummy, so a tool script, a preview or a scene playing in its viewport is heard by nobody. Default true on open, and on restart how the editor being replaced was started: pass false to hear it. The answer's arguments are what the editor was started with. The editor hands no audio driver to a game it plays, so editor_run's own silent decides that game's sound. A hidden editor is headless and silent either way, so its arguments carry no --audio-driver. On restart only for an editor a server opened; one opened by hand comes back as it was."
17407
17483
  }
17408
17484
  },
17409
17485
  requires: [],
@@ -17580,7 +17656,7 @@ var init_tool_definitions = __esm(() => {
17580
17656
  requires: []
17581
17657
  }
17582
17658
  },
17583
- answer: "start: through is gdharness for a game this server started itself, and editor for one the editor plays. scene is the scene the game started in, the project's main scene when none was given. runtime carries addonIsStale and staleNote when the game announced on a runtime addon other than this server's, which no runtime_* call is then sent to, and staleNote says what brings it into step. refreshedClasses names the class_name declarations on disk that the engine's class cache was missing, which were added to it before the game started so the game could resolve them; it is empty when the cache was current. stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). exitedBeforeStop is true when the run was already over when the stop came, so nothing was ended and exitCode, when anything collected it, is the one it ended with. A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings and not the ordinary lines the run printed, which printedLinesNotShown counts; a check given --verbose holds those too, and editor_run start then editor_output reads everything. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is, and durationMs runs from that engine's start to the check's answer, after its exit, so it holds no import that came first. start and check: " + IMPORTED_FIRST + " An exported build carries its own imports and is not asked. waitedForEditorScanMs and scanNote are left out when no editor scan was waited for. A silent windowed run has --audio-driver Dummy in its arguments; a headless run is silent without it, and one the editor plays with sound has no arguments of its own. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. The functions of gdharness's own runtime are not among them: the runtime addon's, which answer its tools inside the game, and those of res://gdharness/loader.gd, which starts it. Their self time is runtimeAddonMs, over runtimeAddonFunctions functions. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. frames counts the frames of the profile that arrived, and the engine's totals also hold the work after the last of them, so once complete a function run every frame can show a call more than frames. firstFrame and lastFrame are the game's own numbers, counted from 0, for the first and the last of them, in a run this server started; a firstFrame above 0 is frames run before the profiler came on, and a gap between the two is frames that never arrived, which the note says. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
17659
+ answer: "start: through is gdharness for a game this server started itself, and editor for one the editor plays. scene is the scene the game started in, the project's main scene when none was given. runtime carries addonIsStale and staleNote when the game announced on a runtime addon other than this server's, which no runtime_* call is then sent to, and staleNote says what brings it into step. refreshedClasses names the class_name declarations on disk that the engine's class cache was missing, which were added to it before the game started so the game could resolve them; it is empty when the cache was current. stop: endedPid is, for a run the editor plays, the number its game announced under, read at the stop rather than remembered, and the operating system hands a freed number to the next process, so it can equal the game before it. stopped is false, with the number under notSignalled, when that number no longer answers as the process the run was started as and nothing was signalled, the note naming which property it failed on, and false with the number under stillGoing when what was told to end was still running ten seconds later. endedThrough says how the stop ended a run that was going: close (the game was asked to close and exited, so exitCode is its own), keeper (the process that started the game ended it through the handle it holds), signal (its pid was signalled once shown to still be the run), or editor (the editor playing it was asked). exitedBeforeStop is true when the run was already over when the stop came, so nothing was ended and exitCode, when anything collected it, is the one it ended with. A start that ends the run before it says how under endedPreviousRunThrough, with the same values, and one that refused to signal that run names the property it failed on under previousRunLeftBecause. check: hung is a run killed at timeoutMs, and errors and warnings count what it printed, which comes back under entries with repeats folded together. entries holds the errors and warnings, and printed the ordinary lines the run printed, in the same shape with repeats folded, the newest 50 kept and printedLinesNotShown counting those left out. A check keeps no run for editor_output to read afterwards; editor_run start then editor_output reads everything. A check given --verbose puts the ordinary lines in entries, with what the engine adds, and has no printed. An engine crash is an error whatever it prints after: the crash handler's report is the entry, and the engine version and the native and GDScript backtraces are its detail, so a crashed run is never booted or clean. arguments is the engine command line the check ran, as a start's is, and durationMs runs from that engine's start to the check's answer, after its exit, so it holds no import that came first. start and check: " + IMPORTED_FIRST + " An exported build carries its own imports and is not asked. waitedForEditorScanMs and scanNote are left out when no editor scan was waited for. A silent windowed run has --audio-driver Dummy in its arguments; a headless run is silent without it, and one the editor plays with sound has no arguments of its own. profile: per function its script, the function (Class.name when the script declares a class_name), the line its body's first statement is on, for a lambda the named function it is written in (within), calls, and self and total milliseconds, with functionsCounted, scriptMs (the self time of them all) and coveredMs. The functions of gdharness's own runtime are not among them: the runtime addon's, which answer its tools inside the game, and those of res://gdharness/loader.gd, which starts it. Their self time is runtimeAddonMs, over runtimeAddonFunctions functions. complete is true when the engine's own totals arrived, which it sends only when the profiler is switched off: the runtime addon does that as a game quits, so a scene that does its work in _ready and quits is covered whole; otherwise the answer is the frames summed so far and the note says what that misses. frames counts the frames of the profile that arrived, and the engine's totals also hold the work after the last of them, so once complete a function run every frame can show a call more than frames. firstFrame and lastFrame are the game's own numbers, counted from 0, for the first and the last of them, in a run this server started; a firstFrame above 0 is frames run before the profiler came on, and a gap between the two is frames that never arrived, which the note says. Self time counts a call into the engine's own methods as the caller's, and a call through super too, while the overridden function's row counts it again: such a row carries selfIncludesSuper, so self times along an override chain overlap. The profiler's own cost of timing a call lands in the self time of the function called; with the runtime addon it is measured each run on an empty function, as perCallOverheadUs, and a row with at least half its selfMs in it carries overheadMs, since a small function called often costs that much less without the profiler. A function the profiler started inside is left out, since its time would be counted from the engine's start. The milliseconds are time passed while the function ran, not processor time given to it, so they grow when other processes compete for the processor: compare two profiles taken under the same load. They are also summed over every thread: a function run by WorkerThreadPool or a Thread on several threads at once counts each thread's time, the engine does not say which thread a call ran on, and a function waiting for them has the wait in its own self time, so selfMs is not time taken from frames, and scriptMs can exceed coveredMs, which the note then says. A property's get and set blocks are named Class.@name_getter and Class.@name_setter, as the engine names them, and their rows carry property, the property's name. A game the editor plays that was not started with profile: true says, in the answer, that it was profiled from the moment it was asked. A profile with mark answers marked: true; one with since answers since: \"mark\" and markSetMsAgo, and lessThanAtMark counts the functions left out for reading less than at the mark, which the engine's own totals, capped, can do when they replace the frames' sum.",
17584
17660
  defaultOperation: "start"
17585
17661
  },
17586
17662
  {
@@ -17644,7 +17720,7 @@ var init_tool_definitions = __esm(() => {
17644
17720
  title: "Editor status",
17645
17721
  effect: "read",
17646
17722
  description: "Whether the editor addon is connected, which Godot answers, whether the editor is playing something, and which games with the runtime addon are reachable, of this project and of every other on the machine. Read connected with mayYetConnect: the editor dials this server and backs off between tries, so for the first half-minute after a server starts false means not reached yet as often as no editor; mayYetConnect true is worth waiting out, false is final, and editorCannotConnect, restartInterrupted, restartUnderway, closedHere and editorsNotYetConnected say why. An editor that has connected and not yet greeted reports nothing about itself, and every other call made in that window waits up to ten seconds for it to settle. playingInEditor null means unanswered rather than nothing playing: processActive and runtimes do not depend on the editor answering. game.run names the run editor_output and editor_run stop answer for, and stays named after it ends. addonIsStale, on a connected editor and on each running game (absent with neither), says it runs addon code other than what this server ships, and staleNote says which half moves: the project, by a gdharness upgrade it spells out to run in a shell, the editor by a restart, or this server by a reconnect. A false editor.lspListening or editor.dapListening comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning, which editor_run waits out before starting a game. A game announced in a protocol this server does not speak is listed under unreadable, during an upgrade that moves the protocol, until the halves match.",
17647
- answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath, and userDir, the directory its user:// is, where its saves and settings are read. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in and took a key out of it, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and from the socket opening until its greeting and that read are done, identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening; when it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears, and hidden that it runs headless with no window anybody can see. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
17723
+ answer: "projectIs, when present, is the gdharness version of the addons in the project, which differs from this server's (project_info says which of the two to move), and configIs, when present, the version the project's MCP config starts, which differs from this server's, so the next connection runs that one. server names this server: its pid, its version, and lifecycleLog, the file every gdharness server of the user appends a line to when it starts, when its client first names itself, when it replaces another server of its project, and when it ends, with why it ended (the client closing its end as stdin:end, a signal by name, superseded with the successor's pid, an uncaught exception or rejection with its stack, or the exit code) and the calls it was answering then. The parent's command line follows on its own line a few seconds after the start. A server killed without warning writes no ended line, and on Windows every kill is that, so a started line with no ended line after it is a server something killed. " + "runtimeConnected counts the games of the project this server serves, and runtimes lists every game on the machine with ofThisProject saying which is which, since a game of another project is reached only by naming its projectPath, and userDir, the directory its user:// is, where its saves and settings are read. playingInEditor is null when the editor was not asked or did not answer, which is not the same as a no: with no editor connected there is nobody to ask, and an editor that does not serve the question is given a moment and then left. A game can be playing while this is null, so read it as unanswered rather than as nothing playing, and use processActive and runtimes, which come from elsewhere and do not depend on the editor answering. Its play says which play the editor is on: play.play counts the plays that editor has started since it opened, whoever started them, and play.editorPid is the editor, so two reads with different pairs are two plays even when both say playing. game.run names the run processActive is about, the one editor_output and editor_run stop answer for: its pid, its project, and adopted true when this server read it back from another server rather than starting it. It stays named after the run ends, beside processActive false, so editor_output can still read what that run printed; null when there is no run at all. startedAt says when it began, and endedAt when it ended, wherever something saw the end: a run adopted from a note another server wrote can have ended hours before this server started. editor.lspListening and editor.dapListening say whether anything accepts connections on the ports the editor names, lspPort and dapPort: Godot binds each once, as the editor starts, and reports a failure only in the log panel of the editor itself, so a false comes with serversNote: editor_launch rebind binds the debug adapter again in place, and editor_launch restart the language server. editor.scan says whether the editor is scanning or importing and how many milliseconds ago its last scan finished (finishedMsAgo), or is null when it did not answer: the editor rewrites the class cache just after a scan, and editor_run waits that out before starting a game, saying so under waitedForEditorScanMs. editor.openScenes lists the scenes open in the editor's tabs, leaving out a tab of a scene never saved, editor.editedScene the one it is showing (null when none is, or when that scene was never saved), and editor.unsavedScenes those with changes not yet written to disk, where an empty entry is a scene never saved; all three come with scan and are absent when it is null. Each game under runtimes is pinged with a short wait, so one held at a breakpoint is listed as unreachable rather than making this call wait out the runtime timeout; for the run this server is reporting on, heldAt says whether that is what it is, with the same three answers editor_output gives. A game the editor played announces the editor under editorPid, and so does whatever that game started for itself; a game some other server started announces none, which is how the two are told apart when both are running from one project. Each game also says which runtime addon it loaded, under addonVersion (null when it cannot say); addonIsStale is true when that code is not what this server ships, and staleNote says whether the project needs the gdharness upgrade command, spelled out to run in a shell, the game a restart, or this server a reconnect. A second checkout of a project, such as a git worktree, carries addons of its own and is where this shows. A runtime_* call to such a game is refused with the staleNote and not sent. When connected is false, mayYetConnect says whether that is final: the editor dials this server rather than the other way round and backs off between tries, so for the first half-minute after a server starts, false means \"not reached yet\" as often as it means \"no editor\". True is worth waiting out; false is an editor that is not there. False beside editorCannotConnect is a project without the gdharness editor addon installed or enabled, where no editor can ever dial in, however young the server or whichever editor is open; its remedies say how to install or enable it. False beside restartInterrupted is one that an editor_launch restart on a previous server asked to quit, where the launch half never ran because that server was ended in between, usually by the reconnect the restart was taken for; editor_launch open finishes it. True beside restartUnderway is a restart still in progress, its server alive and within the wait for the old editor to go: the editor is coming, and opening another would put two on one project. listeningSince is when this server took the bridge port, which is what that window is measured from and what a caller wanting its own window should read. An editor this server opened counts for as long as its process lives, however far past that window the import runs, and awaitingLaunchedEditor names its pid so the two reasons for true are told apart. Any editor of this project running and not yet connected, whoever opened it, makes it true as well and is named under editorsNotYetConnected with its pid and start time, so a restart a reconnect interrupted after its launch is not read as nothing being there. settingsDropped appears here once when the editor this server opened saved project.godot on its way in and took a key out of it, which is the same reading editor_launch restart makes in its own answer. A game that has announced itself in a protocol this server does not speak is listed under unreadable rather than left out, with the protocol it speaks and whether the server or the addon is the older half. This happens during an upgrade that moves the runtime protocol, which most do not: installing replaces the addon on disk as soon as the pin moves, while a server that is already running stays on the version it started with, so the two speak different protocols until the server is reconnected. Those games are real and running, but no tool here can reach them until the halves match, so runtimes stays empty and runtimeConnected stays false. An upgrade that leaves the protocol alone opens no such window and this list stays empty through it. addonIsStale is decided by the code of the editor addons rather than their version, so an editor started before an upgrade that left that code alone reports the older addonVersion, is not stale, and addonNote says so: it needs no restart. A stale editor is asked nothing but to quit: every other call to it is refused with staleNote, which names editor_launch restart, the upgrade command or a reconnect. port is the one the editor bridge is on; when that is not the one this server was configured with, portWanted names the configured one and portNote says why it moved and, when it was the server this one replaced, which pid kept it. A server replacing another of its project that holds the configured port waits for it to stand down and takes the port over, which takes a few seconds; bridgeHandover says so while it waits, and it moves only if that server has not let go within the wait. An editor that has connected and not yet greeted, which lasts a frame and longer while it imports, reports nothing about itself: greeting says so, addonIsStale is absent rather than guessed, and a restart asked then waits for the greeting to learn who opened it. startedByAnEditor is an editor another editor started, by Godot's own restart or the project manager, which has none of the arguments a server gave the one before it; startedByAnEditorNote says what that costs. An engine run that loads the editor addon to do one job and quit, such as a gate's godot --headless --import, an export or a script, is not taken as the editor: a process that greets without a server's mark has its command line read first, and from the socket opening until its greeting and that read are done, identifying is true and connected false; one started with such an option is turned away, and turnedAway names the last one, its pid and the option. Every other call made while an editor is arriving, before its greeting or while it is looked up, waits up to ten seconds for it to settle rather than acting on connected as it reads in that window. serverVersion is the gdharness version of this server. bridgeAvailable says the editor bridge is listening; when it failed to start, startupError gives the error, and retryingBridge is true while this server tries again. announcedAt is the file where this server told editors of its project which port to dial. connectedAt is when the editor connected, and lastPongAt when it last answered this server's keep-alive ping. addonDigest is a hash of the editor addon's code, which addonIsStale compares with what this server ships. debugPort is the port of the editor's remote debugger, the one games it plays connect to. openedByAServer says a gdharness server started this editor, syncsBreakpoints that it keeps its breakpoints when a debug session opens, which Godot otherwise clears, and hidden that it runs headless with no window anybody can see. pendingRequests counts the requests sent to the editor and not yet answered, and queuedResources the files with writes waiting behind another write to the same file.",
17648
17724
  parameters: {},
17649
17725
  requires: []
17650
17726
  },
@@ -17782,7 +17858,7 @@ var init_tool_definitions = __esm(() => {
17782
17858
  namePattern: {
17783
17859
  type: "string",
17784
17860
  ops: ["find"],
17785
- description: 'find: a case-insensitive glob on the node name, such as "Enemy*", or several separated by | the way says takes them, such as "Enemy*|Boss", with \\| for a bar in a name. Matched against the whole name, so a bare word finds only a node called exactly that; an answer of none says how many names contain it and what glob would have found them.'
17861
+ description: 'find: a case-insensitive glob on the node name, such as "Enemy*", or several separated by | the way says takes them, such as "Enemy*|Boss", with \\| for a bar in a name. Matched against the whole name, so a bare word finds only a node called exactly that; an answer of none for a bare word that names contain says how many do and what glob would have found them, and a glob that finds nothing answers a plain none.'
17786
17862
  },
17787
17863
  group: { type: "string", ops: ["find"], description: "find: a group the node is in." },
17788
17864
  says: {
@@ -17978,14 +18054,14 @@ var init_tool_definitions = __esm(() => {
17978
18054
  name: "runtime_input",
17979
18055
  title: "Send input to the game",
17980
18056
  description: "Input to the running game: a whole click on a Control or a 3D node named by path or on a control named by the words on it, an item chosen out of a menu, typing into whatever has the focus, or a raw action, key, mouse button, mouse motion or drag. All of it works headless, where the viewport is the project's window size and the GUI only takes what is inside it. On gdharness's own desktop, where a windowed run goes, Windows refuses to say where the pointer is: every pointer answer there carries pointer_on_hidden_desktop, and the first of the run pointer_note saying why it matters: get_mouse_position() on the root window, and the mouse positions derived from it, read a meaningless position, while what reads the event itself (the control under it, hover, tooltips, input handlers) gets the injected position. A native window no real pointer has entered gets no hover from Godot, so the runtime supplies it there and the answers say hover_supplied.",
17981
- answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again and pressed whether it is left held down (true only after pressed true), for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. key, mouse_click, click, joypad_button and joypad_motion answer actions, the InputMap actions the event also presses; for a motion, those its direction reaches past their deadzone, and none for one inside it. mouse_click answers hovered, the control under the point afterwards; the other events are aimed at nothing. A click that did not land on a control whose mouse_filter is ignore, as a Label's is, says so under why_not_landed, and one on a control with something drawn over it names that, a window stacked above the one the control is in among them. An action, a key, a joypad_button or a joypad_motion answers focused with the path of the Control holding the keyboard focus when it went in, since that Control gets it before the game's _unhandled_input: Space or Enter on a focused button presses the button, and a stick or dpad pressing ui_down moves a focused list's selection, or the focus, and never reaches the game's handler. Where the press moved the focus, focused_after names the Control holding it once the event was handled, or null when nothing does. A mouse_click, mouse_motion or mouse_drag with a point outside the window's viewport says so under outside_viewport, with the viewport's rectangle, since no control there receives it.`,
18057
+ answer: `click: what was under the pointer, and what became of the control: in_tree, removed or freed. A button answers pressed_signal, whether its pressed signal fired during the click. Named by says, found says which match it was, its index being the index argument that presses the same match again, found.item names the tab, list entry, tree row or menu bar title pressed (kind; index, the item's own number in its control, which is not found.index; says; and for a row the column of the cell saying the words), and found.pressedOn says "the words" for words in a RichTextLabel pressed where they are drawn, or "the middle of the label" with a note saying why they could not be placed; on a rich label link_pressed says whether the press reached a link, with link_meta giving the meta the label reports clicked, an empty one included. A control scrolled into view first is scrolled each container out from it a frame after the one inside, and scrolled_into_view says whether a scroll value changed. A button in toggle mode, a CheckBox or CheckButton included, answers pressed with before and after, its button_pressed either side of the click: the same value twice is a click the game undid, and after is null when the click freed it. A refusal for a pointer turned off names where mouse_behavior_recursive was set. On a 3D node landed says the interface did not swallow the press: no control under the pointer stopped it, a control letting the pointer pass included, and every viewport on the way out from the room had the pointer over what shows the room. choose: what was chosen, read before the press, which is heard rather than assumed; what became of the control named (in_tree, removed or freed, since a game can rebuild its screen on the pick); and what the button in front of the menu shows now when it is still there. A menu is opened rather than clicked because its items are drawn rather than built, and the item is looked for as the menu stands once open, since a game can fill a menu as it opens. action, key, mouse_click and joypad_button: the event as it was sent, with whole saying whether the press was let go of again and pressed whether it is left held down (true only after pressed true), for a key the keycode, physical_keycode and unicode it carried, and for a controller button its number, its name and the device. mouse_motion: the position and the relative movement sent. mouse_drag: from, to, motions sent and relative_total. joypad_motion: the axis by number and name, the value and the device. key, mouse_click, click, joypad_button and joypad_motion answer actions, the InputMap actions the event also presses; for a motion, those its direction reaches past their deadzone, and none for one inside it. mouse_click, mouse_motion and mouse_drag answer hovered, the control under the pointer once the events are handled, or null over none; the other events are aimed at nothing. mouse_click with a press, and mouse_drag, answer press_went_to, the path of what the press went to (a control, or a window embedded over the point), read before it, or null when nothing there takes the pointer; given nodePath naming a Control, landed says whether that is the control or inside it. A click that did not land on a control whose mouse_filter is ignore, as a Label's is, says so under why_not_landed, and one on a control with something drawn over it names that, a window stacked above the one the control is in among them. An action, a key, a joypad_button or a joypad_motion answers focused with the path of the Control holding the keyboard focus when it went in, since that Control gets it before the game's _unhandled_input: Space or Enter on a focused button presses the button, and a stick or dpad pressing ui_down moves a focused list's selection, or the focus, and never reaches the game's handler. Where the press moved the focus, focused_after names the Control holding it once the event was handled, or null when nothing does. A mouse_click, mouse_motion or mouse_drag given nodePath answers it under path, its points in the pixels of the window they went to, and that window under window when it is not the game's own. One with a point outside the viewport it went to says so under outside_viewport, with the viewport's rectangle, since no control there receives it.`,
17982
18058
  parameters: {
17983
18059
  projectPath: RUNNING_PROJECT_PATH,
17984
18060
  pid: RUNNING_PID,
17985
18061
  nodePath: {
17986
18062
  type: "string",
17987
- ops: ["click", "choose"],
17988
- description: "click: the Control to click, at its centre, or at the middle of the part on screen when no scroll brings its centre into view (said under pressed_on), or the 3D node to click, where it is drawn; with says, where to look for the control instead, default the whole tree. choose: the PopupMenu, or the OptionButton or MenuButton in front of one."
18063
+ ops: ["click", "choose", "mouse_click", "mouse_motion", "mouse_drag"],
18064
+ description: "click: the Control to click, at its centre, at x and y in its own space when those are given, or at the middle of the part on screen when no scroll brings its centre into view (said under pressed_on), or the 3D node to click, where it is drawn; with says, where to look for the control instead, default the whole tree. choose: the PopupMenu, or the OptionButton or MenuButton in front of one. mouse_click, mouse_motion, mouse_drag: the Control, Node2D or Window whose own space the points are in, a Window's being its own pixels; the events go to the window that shows it, a native one included, which the game's window pixels cannot reach, and the answer names it under window when it is not the game's own. Default the game's window."
17989
18065
  },
17990
18066
  says: {
17991
18067
  type: "string",
@@ -18042,25 +18118,25 @@ var init_tool_definitions = __esm(() => {
18042
18118
  alt: { type: "boolean", ops: ["key"], description: "key: hold Alt with it. Default false." },
18043
18119
  x: {
18044
18120
  type: "number",
18045
- ops: ["mouse_click", "mouse_motion", "mouse_drag"],
18046
- description: "mouse_click, mouse_motion: window pixels. mouse_drag: where the press is."
18121
+ ops: ["click", "mouse_click", "mouse_motion", "mouse_drag"],
18122
+ description: "click: with y, the point to press in the control's own space instead of its centre, for a control that draws its parts itself; not with a 3D node, nor with says finding an item the control draws. mouse_click, mouse_motion: window pixels, or nodePath's own space when that is given. mouse_drag: where the press is, in the same space."
18047
18123
  },
18048
18124
  y: {
18049
18125
  type: "number",
18050
- ops: ["mouse_click", "mouse_motion", "mouse_drag"],
18051
- description: "mouse_click, mouse_motion: window pixels. mouse_drag: where the press is."
18126
+ ops: ["click", "mouse_click", "mouse_motion", "mouse_drag"],
18127
+ description: "click, mouse_click, mouse_motion, mouse_drag: as x."
18052
18128
  },
18053
18129
  toX: {
18054
18130
  type: "number",
18055
18131
  ops: ["mouse_drag"],
18056
- description: "mouse_drag: where the release is, in window pixels."
18132
+ description: "mouse_drag: where the release is, in window pixels or nodePath's own space."
18057
18133
  },
18058
18134
  toY: { type: "number", ops: ["mouse_drag"], description: "mouse_drag: as toX." },
18059
18135
  via: {
18060
18136
  type: "array",
18061
18137
  items: { type: "array", items: { type: "number" }, minItems: 2, maxItems: 2 },
18062
18138
  ops: ["mouse_drag"],
18063
- description: "mouse_drag: points the stroke passes through on the way, in order, each [x, y]. The motions are spread along the whole stroke by distance. Default none: a straight line."
18139
+ description: "mouse_drag: points the stroke passes through on the way, in order, each [x, y] in the space x and y are in. The motions are spread along the whole stroke by distance. Default none: a straight line."
18064
18140
  },
18065
18141
  frames: {
18066
18142
  type: "integer",
@@ -18162,7 +18238,7 @@ var init_tool_definitions = __esm(() => {
18162
18238
  name: "runtime_wait",
18163
18239
  title: "Wait in the running game",
18164
18240
  description: "Lets the running game get on with it and answers when something has happened: a number of frames, a signal, a property reaching a value, or words appearing on a screen. Records a signal or a property from before the call that sets it off, so a change over before the next call arrives is still seen. Needs the game running with the runtime addon.",
18165
- answer: "recorded: changes, each with frame (process frames since the watch was armed, 0 the reading at arming: a process frame is no set time, and a headless game, drawing nothing, runs them far faster than the sixty physics ticks a second that _physics_process sees, so game_time is the clock), elapsed_ms, game_time (the seconds of process time the game had been given by that frame, scaled by Engine.time_scale as a tween is) and value; frames and elapsed_ms say how long it recorded. calls lists each runtime_invoke set or call and runtime_input sent while it recorded, in order, with call (the tool and op), what it was given of path, method, property, action, keycode, button, pressed and says, and frame, the last reading taken before it ran, so what it changed shows from the next reading at the earliest. Two calls sent one after the other arrive frames apart, which frame tells apart from a delay in the game itself; calls_dropped counts any past 1000. dropped says so when more than 1000 changes came, and ended when recording stopped before it was collected: the node was freed, the path stopped reading, or 120000 ms passed.",
18241
+ answer: "recorded: changes, each with frame (process frames since the watch was armed, 0 the reading at arming: a process frame is no set time, and a headless game, drawing nothing, runs them far faster than the sixty physics ticks a second that _physics_process sees, so game_time is the clock), elapsed_ms (wall-clock milliseconds since the arming), game_time (the seconds of process time the game had been given between the arming and that frame, scaled by Engine.time_scale as a tween is; like frame, it counts from the watch, not from the start of the game) and value; frames and elapsed_ms say how long it recorded. calls lists each runtime_invoke set or call and runtime_input sent while it recorded, in order, with call (the tool and op), what it was given of path, method, property, action, keycode, button, pressed and says, and frame, the last reading taken before it ran, so what it changed shows from the next reading at the earliest. Two calls sent one after the other arrive frames apart, which frame tells apart from a delay in the game itself; calls_dropped counts any past 1000. dropped says so when more than 1000 changes came, and ended when recording stopped before it was collected: the node was freed, the path stopped reading, or 120000 ms passed.",
18166
18242
  parameters: {
18167
18243
  projectPath: RUNNING_PROJECT_PATH,
18168
18244
  pid: RUNNING_PID,
@@ -18187,7 +18263,7 @@ var init_tool_definitions = __esm(() => {
18187
18263
  type: "integer",
18188
18264
  minimum: 1,
18189
18265
  ops: ["signal", "recorded"],
18190
- description: "signal: the watch an earlier watch of a signal answered, in place of nodePath and signal. Answers at once, with fired_before_wait, when the signal fired after the watch was armed, and otherwise waits for it; times counts how often it fired, and args is what the first carried. recorded: the watch an earlier watch of a property answered. The watch goes with the answer either way."
18266
+ description: "signal: the watch an earlier watch of a signal answered, in place of nodePath and signal. Answers at once, with fired_before_wait, when the signal fired after the watch was armed, and otherwise waits for it; times counts how often it fired, args is what the first carried, fired_after_ms the milliseconds from the arming to the first, and elapsed_ms how long the wait itself took. recorded: the watch an earlier watch of a property answered. The watch goes with the answer either way."
18191
18267
  },
18192
18268
  property: {
18193
18269
  type: "string",
@@ -18237,7 +18313,7 @@ var init_tool_definitions = __esm(() => {
18237
18313
  },
18238
18314
  recorded: {
18239
18315
  effect: "read",
18240
- summary: "answer what a watch of a property recorded since it was armed, at once: changes holds the reading at arming and every later one that differed, each with the frame, elapsed_ms and game_time it was read at, and calls every runtime_invoke set or call and runtime_input sent while it recorded, each with the frame of the last reading before it ran",
18316
+ summary: "answer what a watch of a property recorded since it was armed, at once: changes holds the reading at arming and every later one that differed, each with the frame, elapsed_ms and game_time it was read at, and calls every runtime_invoke set or call and runtime_input sent while it recorded, each with the frame of the last reading before it ran. A property that changes every frame, such as the position of something moving, records one change a frame up to 1000, an answer of tens of thousands of characters; for the moment it crosses a value, until with compare answers that alone",
18241
18317
  requires: ["watch"]
18242
18318
  },
18243
18319
  until: {
@@ -43351,18 +43427,19 @@ async function renameSymbol(request, services) {
43351
43427
  }
43352
43428
  }
43353
43429
  const heirs = graph.descendants(root);
43430
+ const declaresNewName = (body) => [body, ...graph.ancestors(body)].some((id) => graph.declaration(id, newName) !== undefined);
43354
43431
  const inUse = [];
43355
43432
  for (const script of scripts.values()) {
43356
43433
  for (const occurrence of script.occurrences(newName)) {
43357
43434
  const body = graph.bodyAt(script.path, occurrence.offset);
43358
- if (occurrence.kind === "code" && !occurrence.afterDot && heirs.has(body) && graph.declaration(body, newName)?.offset !== occurrence.offset) {
43435
+ if (occurrence.kind === "code" && !occurrence.afterDot && heirs.has(body) && !declaresNewName(body)) {
43359
43436
  inUse.push({ file: script.path, line: script.lines.positionOf(occurrence.offset).line + 1 });
43360
43437
  }
43361
43438
  }
43362
43439
  }
43363
43440
  if (inUse.length > 0) {
43364
43441
  const places = inUse.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line).slice(0, 10).map((place) => `${place.file}:${place.line}`);
43365
- conflicts.push(`${newName} is already used in code where ${symbol} is inherited, as a local, a parameter or another name the renamed ${what} would shadow or be shadowed by, at ${places.join(", ")}${inUse.length > places.length ? ` and ${inUse.length - places.length} more` : ""}`);
43442
+ conflicts.push(`${newName} is already used in code in a class that declares or inherits ${symbol}, as a local, a parameter or another name the renamed ${what} would shadow or be shadowed by, at ${places.join(", ")}${inUse.length > places.length ? ` and ${inUse.length - places.length} more` : ""}`);
43366
43443
  }
43367
43444
  if (conflicts.length > 0) {
43368
43445
  return {
@@ -43439,8 +43516,8 @@ async function renameSymbol(request, services) {
43439
43516
  }, plan);
43440
43517
  }
43441
43518
  plan = mentionIgnored(plan, ignoredTexts(projectPath), symbol, moveTo === null ? [] : [declaringPath]);
43442
- const left = leftAloneReport(plan.mentions);
43443
43519
  const rewrites = rewritesOf(plan, texts, moveTo === null || request.newScriptPath === null ? null : { from: declaringPath, to: moveTo, absoluteTo: request.newScriptPath });
43520
+ const left = leftAloneReport(asWritten(plan.mentions, rewrites));
43444
43521
  const graph = new ClassGraph(scripts, declared);
43445
43522
  const rank = (path) => (declaringFiles.includes(path) ? 0 : 1000) + graph.ancestors(`${path}#`).length;
43446
43523
  const scriptsInOrder = rewrites.map((rewrite) => rewrite.path).filter((path) => path.endsWith(".gd")).sort((a, b) => rank(a) - rank(b) || a.localeCompare(b)).map((path) => path === declaringPath && moveTo !== null ? moveTo : path);
@@ -43553,6 +43630,14 @@ function changedReport(edits, rewrites) {
43553
43630
  };
43554
43631
  });
43555
43632
  }
43633
+ function asWritten(mentions, rewrites) {
43634
+ const after = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite.after.split(`
43635
+ `)]));
43636
+ return mentions.map((mention) => {
43637
+ const line = mention.line < 1 ? undefined : after.get(mention.file)?.[mention.line - 1];
43638
+ return line === undefined ? mention : { ...mention, text: line.replace(/\r$/, "").trim() };
43639
+ });
43640
+ }
43556
43641
  function leftAloneReport(mentions) {
43557
43642
  const full = [];
43558
43643
  const prose = new Map;
@@ -43859,12 +43944,12 @@ function planFileMove(projectPath, absoluteFrom, absoluteTo) {
43859
43944
  }
43860
43945
  }
43861
43946
  const afterwards = (path) => moves.get(path) ?? path;
43862
- const mentions = [...plan.mentions, ...binaries].map((mention) => ({
43947
+ const rewrites = rewritesOf(plan, texts, null);
43948
+ const mentions = asWritten([...plan.mentions, ...binaries], rewrites).map((mention) => ({
43863
43949
  ...mention,
43864
43950
  file: afterwards(mention.file)
43865
43951
  }));
43866
43952
  const left = leftAloneReport(mentions);
43867
- const rewrites = rewritesOf(plan, texts, null);
43868
43953
  const absoluteOf = (path) => join37(projectPath, path.slice("res://".length));
43869
43954
  const rewritten = new Map(rewrites.map((rewrite) => [rewrite.path, rewrite]));
43870
43955
  const moved = [...moves].map(([source, target]) => {
@@ -50274,16 +50359,31 @@ function scriptsToDiagnose(projectPath, given) {
50274
50359
  }
50275
50360
  const inside = folderFiles(location.absolutePath, [".gd"]).map((script) => relative === "" ? script : `${relative}/${script}`);
50276
50361
  if (inside.length === 0) {
50277
- empty.push(entry);
50362
+ empty.push(noScriptUnder(entry, location.absolutePath, relative));
50278
50363
  }
50279
50364
  found.push(...inside);
50280
50365
  }
50281
50366
  const unique = [...new Set(found)];
50282
50367
  if (unique.length === 0) {
50283
- return { ok: false, reason: `No .gd script is under ${empty.join(" or ")}.` };
50368
+ return { ok: false, reason: empty.join(" ") };
50284
50369
  }
50285
50370
  return { ok: true, value: unique };
50286
50371
  }
50372
+ function scriptsUnderGdignore(projectPath, scripts) {
50373
+ const ignored = scripts.map((script) => script.replace(/^res:\/\//, "")).filter((script) => underGdignore(projectPath, script)).map((script) => `res://${script}`);
50374
+ return ignored.length === 0 ? {} : {
50375
+ underGdignore: ignored,
50376
+ underGdignoreNote: "The engine never loads a script under a folder holding a .gdignore, so what is reported for these does not stop the project from compiling or running."
50377
+ };
50378
+ }
50379
+ function noScriptUnder(entry, absolute, relative) {
50380
+ const said = (stepped) => {
50381
+ const folder = [relative, stepped.folder].filter((step) => step !== "").join("/") || entry;
50382
+ return stepped.why === "gdharness" ? `${folder} is an addon gdharness installed, whose scripts are checked when it is named` : `${folder} holds a .gdignore, so the engine never loads what is under it, and a script there is checked when the script is named`;
50383
+ };
50384
+ const stepped = steppedOverIn(absolute);
50385
+ return stepped.length === 0 ? `No .gd script is under ${entry}.` : `No .gd script is under ${entry} that a folder walk takes in: ${stepped.map(said).join("; ")}.`;
50386
+ }
50287
50387
  function engineChecked(why) {
50288
50388
  return {
50289
50389
  checkedBy: "engine",
@@ -50329,6 +50429,7 @@ class GodotServer {
50329
50429
  godotBridge;
50330
50430
  tools = buildToolDefinitions();
50331
50431
  activeProcess = null;
50432
+ lastCheckAt = null;
50332
50433
  updates = new UpdateCheck(SERVER_VERSION);
50333
50434
  noticedUpdate = false;
50334
50435
  callsSinceNotice = 0;
@@ -51315,7 +51416,9 @@ class GodotServer {
51315
51416
  button: args["button"] ?? "left",
51316
51417
  double: readBoolean(args, "doubleClick") ?? false,
51317
51418
  ...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
51318
- ...args["index"] === undefined ? {} : { index: args["index"] }
51419
+ ...args["index"] === undefined ? {} : { index: args["index"] },
51420
+ ...args["x"] === undefined ? {} : { x: args["x"] },
51421
+ ...args["y"] === undefined ? {} : { y: args["y"] }
51319
51422
  }, { tool: "runtime_input", op });
51320
51423
  }
51321
51424
  if (op === "choose") {
@@ -53014,6 +53117,7 @@ ${error.text}`;
53014
53117
  }
53015
53118
  if (scripts?.value == null) {
53016
53119
  const asked = readString(args, "scriptPath");
53120
+ let ignored = {};
53017
53121
  if (project.ok && asked !== undefined) {
53018
53122
  const location = resolveWithinProject(project.value.path, asked);
53019
53123
  if (!location.ok) {
@@ -53022,13 +53126,22 @@ ${error.text}`;
53022
53126
  if (!existsSync34(location.absolutePath)) {
53023
53127
  return this.missingScript(project.value.path, location.relativePath);
53024
53128
  }
53129
+ ignored = scriptsUnderGdignore(project.value.path, [location.relativePath]);
53025
53130
  }
53026
53131
  const one = await this.diagnosticsOf(args);
53027
53132
  if ("noLanguageServer" in one) {
53028
53133
  const checked = project.ok ? await this.engineDiagnostics(project.value.path, [readString(args, "scriptPath") ?? ""], one.noLanguageServer) : { refusal: project.response };
53029
- return "refusal" in checked ? checked.refusal : this.jsonTextResponse({ ...checked.answers[0], ...engineChecked(one.noLanguageServer.reason) });
53134
+ return "refusal" in checked ? checked.refusal : this.jsonTextResponse({
53135
+ ...checked.answers[0],
53136
+ ...engineChecked(one.noLanguageServer.reason),
53137
+ ...ignored
53138
+ });
53030
53139
  }
53031
- return "refusal" in one ? one.refusal : this.jsonTextResponse(one.answer);
53140
+ return "refusal" in one ? one.refusal : this.jsonTextResponse({
53141
+ ...one.answer,
53142
+ checkedBy: "language server",
53143
+ ...ignored
53144
+ });
53032
53145
  }
53033
53146
  const progress = callProgress();
53034
53147
  const cancelled = callSignal();
@@ -53038,6 +53151,7 @@ ${error.text}`;
53038
53151
  let warnings = 0;
53039
53152
  let unread = 0;
53040
53153
  let byEngine = null;
53154
+ let byServer = 0;
53041
53155
  for (const [at, scriptPath] of scripts.value.entries()) {
53042
53156
  if (cancelled?.aborted === true) {
53043
53157
  return this.createErrorResponse(`Cancelled after ${at} of ${scripts.value.length} scripts.`);
@@ -53070,6 +53184,7 @@ ${error.text}`;
53070
53184
  reported.push({ scriptPath, error: one.refusal.content[0]?.text ?? "not read" });
53071
53185
  continue;
53072
53186
  }
53187
+ byServer += 1;
53073
53188
  errors += Number(one.answer["errors"]);
53074
53189
  warnings += Number(one.answer["warnings"]);
53075
53190
  if (readArray(one.answer, "diagnostics")?.length === 0) {
@@ -53086,7 +53201,9 @@ ${error.text}`;
53086
53201
  ...unread > 0 ? { scriptsNotRead: unread } : {},
53087
53202
  scripts: reported,
53088
53203
  nothingReported,
53089
- ...byEngine === null ? {} : engineChecked(byEngine)
53204
+ ...byEngine === null ? { checkedBy: "language server" } : engineChecked(byEngine),
53205
+ ...byEngine !== null && byServer > 0 ? { checkedBy: "language server, then engine", checkedByLanguageServer: byServer } : {},
53206
+ ...project.ok ? scriptsUnderGdignore(project.value.path, scripts.value) : {}
53090
53207
  });
53091
53208
  }
53092
53209
  missingScript(projectPath, relativePath) {
@@ -53628,10 +53745,19 @@ ${error.text}`;
53628
53745
  return null;
53629
53746
  }
53630
53747
  const since = readNumber(asParams(answer), "sinceScanFinishedMs");
53748
+ const open = readStringArray(asParams(answer), "openScenes");
53749
+ const edited = readString(asParams(answer), "editedScene");
53631
53750
  return {
53632
53751
  scanning: readBoolean(asParams(answer), "scanning") ?? false,
53633
53752
  importing: readBoolean(asParams(answer), "importing") ?? false,
53634
- finishedMsAgo: since === undefined || since < 0 ? null : since
53753
+ finishedMsAgo: since === undefined || since < 0 ? null : since,
53754
+ ...open === undefined ? {} : {
53755
+ scenes: {
53756
+ openScenes: open,
53757
+ editedScene: edited === undefined || edited === "" ? null : edited,
53758
+ unsavedScenes: readStringArray(asParams(answer), "unsavedScenes") ?? []
53759
+ }
53760
+ }
53635
53761
  };
53636
53762
  } catch {
53637
53763
  return null;
@@ -53673,7 +53799,7 @@ ${error.text}`;
53673
53799
  problem: reply.ok ? null : reply.message
53674
53800
  };
53675
53801
  }));
53676
- const asked = (async () => {
53802
+ const askEditor = async () => {
53677
53803
  const playing = await this.editorPlayingState();
53678
53804
  await this.pickUpWhatTheEditorIsPlaying();
53679
53805
  const current = await this.currentRun();
@@ -53684,14 +53810,20 @@ ${error.text}`;
53684
53810
  run: current === null ? null : this.runNamed(current),
53685
53811
  hold: current === null ? {} : await this.holdOf(current, active)
53686
53812
  };
53687
- })();
53688
- const [games, { playing, active, run, hold }, scan, editorStatus, servers] = await Promise.all([
53813
+ };
53814
+ const connectedBefore = this.godotBridge.getStatus().connected;
53815
+ const [games, askedFirst, scanFirst, editorStatus, serversFirst] = await Promise.all([
53689
53816
  pinged,
53690
- asked,
53817
+ askEditor(),
53691
53818
  this.editorScanState(),
53692
53819
  this.getEditorStatusPayload(),
53693
53820
  this.editorServersListening()
53694
53821
  ]);
53822
+ const askAgain = !connectedBefore && editorStatus.connected;
53823
+ if (askAgain) {
53824
+ this.logDebug("editor_status: the editor connected during the call, so it is asked again");
53825
+ }
53826
+ const [{ playing, active, run, hold }, scan, servers] = askAgain ? await Promise.all([askEditor(), this.editorScanState(), this.editorServersListening()]) : [askedFirst, scanFirst, serversFirst];
53695
53827
  const answeredAt = Date.now();
53696
53828
  const inFlight = this.godotBridge.requestsInFlight().map((request) => ({ tool: request.tool, forMs: answeredAt - request.startedAt }));
53697
53829
  return this.jsonTextResponse({
@@ -53701,7 +53833,8 @@ ${error.text}`;
53701
53833
  pendingRequests: inFlight.length,
53702
53834
  pendingRequestTools: inFlight.length === 0 ? undefined : inFlight,
53703
53835
  debugPort: playing?.debugPort ?? this.godotBridge.getStatus().debugPort,
53704
- scan
53836
+ scan: scan === null ? null : { scanning: scan.scanning, importing: scan.importing, finishedMsAgo: scan.finishedMsAgo },
53837
+ ...scan?.scenes
53705
53838
  },
53706
53839
  godot: {
53707
53840
  path: located.ok ? located.path : null,
@@ -54451,8 +54584,16 @@ ${error.text}`;
54451
54584
  return answer;
54452
54585
  }
54453
54586
  const startedAt = Date.now();
54587
+ const steps = [];
54588
+ let stepFrom = startedAt;
54589
+ const step = (name) => {
54590
+ const now = Date.now();
54591
+ steps.push(`${name} ${now - stepFrom}ms`);
54592
+ stepFrom = now;
54593
+ };
54454
54594
  const transcript = openTranscript(startedAt);
54455
54595
  closeSync5(transcript.fd);
54596
+ step("transcript");
54456
54597
  const playAnswer = asParams(JSON.parse(answer.content[0]?.text ?? "{}"));
54457
54598
  const which = playOf(playAnswer);
54458
54599
  const played = {
@@ -54473,6 +54614,7 @@ ${error.text}`;
54473
54614
  };
54474
54615
  this.activeProcess = played;
54475
54616
  this.noteTheLatestRun({ projectPath, pid: null, startedAt });
54617
+ step("latest run");
54476
54618
  this.watchThePlayedConsole();
54477
54619
  writeEditorRunNote({
54478
54620
  projectPath,
@@ -54480,7 +54622,10 @@ ${error.text}`;
54480
54622
  startedAt,
54481
54623
  ...which === null ? {} : { play: which }
54482
54624
  });
54625
+ step("run note");
54483
54626
  sweepTranscripts();
54627
+ step("sweep");
54628
+ this.logDebug(`After the play: ${steps.join(", ")}`);
54484
54629
  return this.jsonTextResponse({
54485
54630
  started: true,
54486
54631
  through: "editor",
@@ -55199,7 +55344,8 @@ ${error.text}`;
55199
55344
  const mine = this.ourProject();
55200
55345
  const record = everyRunRecord().find((one) => mine === null || !isSameDirectory(one.projectPath, mine)) ?? null;
55201
55346
  if (record === null) {
55202
- return `No game is running.${noEditor} Start one with editor_run.`;
55347
+ const checked = this.lastCheckAt === null ? "" : ` The editor_run check ${Math.round((Date.now() - this.lastCheckAt) / 1000)}s ago kept no run: its answer held what the game printed, under entries and printed.`;
55348
+ return `No game is running.${checked}${noEditor} Start one with editor_run.`;
55203
55349
  }
55204
55350
  const ours = mine === null ? "this server was started without GDHARNESS_PROJECT and has no editor connected, so it cannot say whose that run is" : `this server serves ${mine}`;
55205
55351
  return `No game of this server's is running.${noEditor} A run started from ${record.projectPath} is recorded on this machine, and ${ours}, so it is not this server's to answer for or to end. Start one with editor_run.`;
@@ -55285,7 +55431,10 @@ ${error.text}`;
55285
55431
  const errors = boot.log.count("error");
55286
55432
  const warnings = boot.log.count("warning");
55287
55433
  const floor = engineArgs.includes("--verbose") ? "info" : "warning";
55288
- const printed = boot.log.count("info");
55434
+ const ordinary = boot.log.all.filter((entry) => entry.severity === "info");
55435
+ const printed = floor === "warning" ? foldedRepeats(ordinary).slice(-MOST_PRINTED_LINES) : [];
55436
+ const shown = printed.reduce((sum, entry) => sum + (entry.times ?? 1), 0);
55437
+ this.lastCheckAt = Date.now();
55289
55438
  return this.jsonTextResponse({
55290
55439
  booted: !hung && boot.exitCode === 0 && errors === 0,
55291
55440
  hung,
@@ -55299,7 +55448,8 @@ ${error.text}`;
55299
55448
  argsNote: engineFlagsNote(userArgs),
55300
55449
  ...scanWaitAnswer(scanned),
55301
55450
  savesNote: asked.savesIn !== undefined && savesStayPut() ? SAVES_NOT_MOVED_NOTE : undefined,
55302
- printedLinesNotShown: floor === "warning" && printed > 0 ? printed : undefined,
55451
+ printed: printed.length > 0 ? printed : undefined,
55452
+ printedLinesNotShown: floor === "warning" && ordinary.length > shown ? ordinary.length - shown : undefined,
55303
55453
  ...foldedEntriesAnswer(boot.log, floor)
55304
55454
  });
55305
55455
  }
@@ -56464,7 +56614,24 @@ ${error.text}`;
56464
56614
  }
56465
56615
  }
56466
56616
  });
56467
- return this.godotBridge.isConnected() ? await this.alsoSayWhatTheEditorCannotSee(answered, args) : answered;
56617
+ if (!this.godotBridge.isConnected()) {
56618
+ return answered;
56619
+ }
56620
+ const scanned = answered.isError === true || this.editorWritesAShortList(project.value.path) ? null : await this.handleRescanFilesystem({ projectPath: project.value.path });
56621
+ return await this.alsoSayWhatTheEditorCannotSee(scanned === null ? answered : this.withEditorScan(answered, scanned), args);
56622
+ }
56623
+ withEditorScan(answered, scanned) {
56624
+ const payload = asParams(JSON.parse(answered.content[0]?.text ?? "{}"));
56625
+ const scan = scanned.isError === true ? null : asParams(JSON.parse(scanned.content[0]?.text ?? "{}"));
56626
+ const broughtIn = scan === null ? undefined : readStringArray(scan, "broughtIn");
56627
+ return this.jsonTextResponse({
56628
+ ...payload,
56629
+ editorRescanned: scan !== null && readBoolean(scan, "ok") === true,
56630
+ ...broughtIn === undefined ? {} : { broughtIn },
56631
+ ...scan === null ? {
56632
+ editorRescanNote: `The editor was asked to scan for the new class and answered: ${scanned.content[0]?.text ?? "nothing"}`
56633
+ } : readBoolean(scan, "ok") === true ? {} : { editorRescanNote: readString(scan, "note") ?? JSON.stringify(scan) }
56634
+ });
56468
56635
  }
56469
56636
  async handleModifyScript(args) {
56470
56637
  const written = await this.headless("modify_script", args);
@@ -57445,7 +57612,7 @@ async function runGodotServer() {
57445
57612
  const server = new GodotServer;
57446
57613
  await server.run();
57447
57614
  }
57448
- 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_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_UNDECLARED_ERRORS = 20, ERROR_ASSERT = "GdUnitGodotErrorAssertImpl.gd", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
57615
+ 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_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", EXTERNAL_MEMBER = 'Could not resolve external class member "', STATIC_LIST_HINT;
57449
57616
  var init_server = __esm(() => {
57450
57617
  init_dist2();
57451
57618
  init_stdio();
@@ -58280,7 +58447,7 @@ async function runSpreadCommand(options, godotPath) {
58280
58447
  mkdirSync8(shareDirectory, { recursive: true });
58281
58448
  const chosen = machineSlots({ directory: options.slotsDirectory, count: options.slotCount });
58282
58449
  const slotCount = chosen.count;
58283
- const slots = new EngineSlots(chosen.directory, slotCount, { pid: process13.pid, started: await ownStart() }, (line) => {
58450
+ const slots = new EngineSlots(chosen.directory, slotCount, { pid: process13.pid, started: await ownStart(), by: `gdharness spread of ${options.projectPath}` }, (line) => {
58284
58451
  console.log(`[spread] ${line}`);
58285
58452
  });
58286
58453
  const launch = {
@@ -58830,7 +58997,7 @@ async function runTestCommand(options, godotPath) {
58830
58997
  mkdirSync10(runDirectory, { recursive: true });
58831
58998
  const logs = new RunLogs(runDirectory, keptRuns === testRuns ? null : join27(keptRuns, runName));
58832
58999
  const chosen = machineSlots({ directory: options.slotsDirectory, count: options.slotCount });
58833
- const slots = new EngineSlots(chosen.directory, chosen.count, { pid: process14.pid, started: await ownStart() }, (line) => {
59000
+ const slots = new EngineSlots(chosen.directory, chosen.count, { pid: process14.pid, started: await ownStart(), by: `gdharness test of ${options.projectPath}` }, (line) => {
58834
59001
  console.log(line);
58835
59002
  });
58836
59003
  const running = new Set;
@@ -59828,6 +59995,8 @@ async function main() {
59828
59995
  console.error(`gdharness: ${line}`);
59829
59996
  });
59830
59997
  }
59998
+ const served = process.env["GDHARNESS_PROJECT"];
59999
+ nameSlotTaker(command !== undefined ? `gdharness ${command}` : `a gdharness server${served === undefined || served === "" ? "" : ` of ${served}`}`);
59831
60000
  if (command === "test") {
59832
60001
  await test();
59833
60002
  return;