gdharness 1.3.8 → 1.3.9

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 (35) hide show
  1. package/build/cli.js +137 -66
  2. package/build/godot/addons/gdharness_editor/file_reads.gd +13 -0
  3. package/build/godot/addons/gdharness_editor/reading.gd +61 -5
  4. package/build/godot/addons/gdharness_editor/scene_file.gd +41 -35
  5. package/build/godot/addons/gdharness_editor/serialisation.gd +8 -7
  6. package/build/godot/addons/gdharness_editor/tool_executor.gd +12 -2
  7. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +244 -0
  8. package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
  9. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +105 -157
  10. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +25 -1
  11. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -1
  12. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
  13. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +72 -3
  14. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +7 -1
  15. package/build/godot/addons/gdharness_runtime/call_errors.gd +8 -2
  16. package/build/godot/addons/gdharness_runtime/reading.gd +61 -5
  17. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
  18. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +3 -0
  19. package/build/godot/addons/gdharness_runtime/runtime_input.gd +27 -31
  20. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +11 -5
  21. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +2 -0
  22. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +26 -0
  23. package/build/godot/addons/gdharness_runtime/runtime_values.gd +105 -6
  24. package/build/godot/addons/gdharness_runtime/serialisation.gd +8 -7
  25. package/build/godot/operations/editor_tools.gd +8 -0
  26. package/build/godot/operations/file_reads.gd +13 -0
  27. package/build/godot/operations/gdscript_analysis.gd +14 -0
  28. package/build/godot/operations/gdscript_authoring.gd +1 -1
  29. package/build/godot/operations/godot_operations.gd +2 -0
  30. package/build/godot/operations/input_actions.gd +22 -7
  31. package/build/godot/operations/project_config.gd +31 -0
  32. package/build/godot/operations/reading.gd +61 -5
  33. package/build/godot/operations/serialisation.gd +8 -7
  34. package/build/keeper.js +43 -13
  35. package/package.json +1 -1
package/build/cli.js CHANGED
@@ -8415,24 +8415,30 @@ function projectStructure(projectPath) {
8415
8415
  return structure;
8416
8416
  }
8417
8417
  function projectFiles(projectPath, extensions) {
8418
+ return filesBelow(projectPath, extensions);
8419
+ }
8420
+ function folderFiles(directory, extensions) {
8421
+ return gdignored(directory) ? [] : filesBelow(directory, extensions);
8422
+ }
8423
+ function filesBelow(root, extensions) {
8418
8424
  const found = [];
8419
8425
  const visit = (directory, prefix) => {
8420
- if (steppedOver(directory)) {
8421
- return;
8422
- }
8423
8426
  for (const entry of readdirSync4(directory, { withFileTypes: true })) {
8424
8427
  if (skipped(entry.name)) {
8425
8428
  continue;
8426
8429
  }
8427
8430
  const spelled = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
8431
+ const path = join7(directory, entry.name);
8428
8432
  if (entry.isDirectory()) {
8429
- visit(join7(directory, entry.name), spelled);
8433
+ if (!steppedOver(path)) {
8434
+ visit(path, spelled);
8435
+ }
8430
8436
  } else if (entry.isFile() && extensions.includes(extname(entry.name).toLowerCase())) {
8431
8437
  found.push(spelled);
8432
8438
  }
8433
8439
  }
8434
8440
  };
8435
- visit(projectPath, "");
8441
+ visit(root, "");
8436
8442
  return found.sort();
8437
8443
  }
8438
8444
  function scriptsWithoutUid(projectPath) {
@@ -12238,9 +12244,23 @@ var init_tool_definitions = __esm(() => {
12238
12244
  };
12239
12245
  ANIMATION_TRACK = {
12240
12246
  type: "object",
12241
- description: "The track to add.",
12247
+ description: "add_track: the track to add. remove_track: the track to take out, found by its type, nodePath and property as read answers them; its keyframes are not needed.",
12242
12248
  properties: {
12243
- type: { type: "string", enum: ["property", "method"] },
12249
+ type: {
12250
+ type: "string",
12251
+ enum: [
12252
+ "property",
12253
+ "method",
12254
+ "position_3d",
12255
+ "rotation_3d",
12256
+ "scale_3d",
12257
+ "blend_shape",
12258
+ "bezier",
12259
+ "audio",
12260
+ "animation"
12261
+ ],
12262
+ description: "add_track makes property and method tracks; remove_track takes any type read answers."
12263
+ },
12244
12264
  nodePath: { type: "string", description: "Target node, relative to the AnimationPlayer root." },
12245
12265
  property: { type: "string", description: "For property tracks: the property to animate." },
12246
12266
  method: {
@@ -12249,6 +12269,7 @@ var init_tool_definitions = __esm(() => {
12249
12269
  },
12250
12270
  keyframes: {
12251
12271
  type: "array",
12272
+ description: "add_track: the keys to add, each at its own time.",
12252
12273
  items: {
12253
12274
  type: "object",
12254
12275
  properties: {
@@ -12261,7 +12282,7 @@ var init_tool_definitions = __esm(() => {
12261
12282
  }
12262
12283
  }
12263
12284
  },
12264
- required: ["type", "nodePath", "keyframes"],
12285
+ required: ["type", "nodePath"],
12265
12286
  additionalProperties: false
12266
12287
  };
12267
12288
  SCRIPT_MODIFICATIONS = {
@@ -12301,7 +12322,7 @@ var init_tool_definitions = __esm(() => {
12301
12322
  };
12302
12323
  INPUT_EVENTS = {
12303
12324
  type: "array",
12304
- description: "The events that trigger the action.",
12325
+ description: "The events that trigger the action, each bound to every device, as the editor binds them. A read names device only on an event bound to one device.",
12305
12326
  items: {
12306
12327
  type: "object",
12307
12328
  properties: {
@@ -12312,7 +12333,11 @@ var init_tool_definitions = __esm(() => {
12312
12333
  axisValue: { type: "number", description: "Joypad axes: -1 or 1." },
12313
12334
  ctrl: { type: "boolean", description: "Keys: bind it with Ctrl held." },
12314
12335
  alt: { type: "boolean", description: "Keys: bind it with Alt held." },
12315
- shift: { type: "boolean", description: "Keys: bind it with Shift held." }
12336
+ shift: { type: "boolean", description: "Keys: bind it with Shift held." },
12337
+ physical: {
12338
+ type: "boolean",
12339
+ description: "Keys: bind the key by where it sits on the keyboard (physical_keycode) rather than by what it types, as the editor offers, so W stays the key above S on any layout. Default false."
12340
+ }
12316
12341
  },
12317
12342
  required: ["type"],
12318
12343
  additionalProperties: false
@@ -12380,12 +12405,12 @@ var init_tool_definitions = __esm(() => {
12380
12405
  {
12381
12406
  name: "project_settings",
12382
12407
  title: "Project settings",
12383
- description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. project_info include lists the autoloads, input actions and audio buses as the project holds them. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, or one on another release's addon that was not asked, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events and deadzone, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. Every write keeps the sections and keys of project.godot in the order the file had them, so a change moves no other line; the save is the engine's, and like the editor's it writes Godot's header and config/features into a file that lacks them. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
12408
+ description: "Reads or writes project.godot: settings, autoloads, the main scene, input actions, plugins and audio buses. project_info include lists the autoloads, input actions and audio buses as the project holds them. A write with an editor open on the project is handed to the editor, which takes the settings the write changed or names from the file (named under editorAdopted), including a setting the file already held, since the editor can hold another value after the file was put back, or reloads the bus layout, so it answers the new values and does not save the old ones back over them. editorNote is an editor that could not take the write, or one on another release's addon that was not asked, which still holds the old values until editor_launch restart. An enabled plugin's code still loads on the editor's next start. set answers old_value and new_value, with was_new true when project.godot did not set the setting before, and was_default true when what it held was the engine's own default, which old_value then is. add_input_action on an action that exists, one of the engine's ui_ actions included, replaces its events, and its deadzone when one is given, and answers was_new false with what it held under old_events and old_deadzone. A set of the value the setting already holds, in the same type, writes nothing and answers unchanged true and saved false. unset takes a setting's line out of project.godot, the way back from a set of a misspelt name, and answers removed, old_value and default_value, the value that holds now (null for a setting the engine does not declare); on one the file does not set it answers removed false and writes nothing. Every write keeps the sections and keys of project.godot in the order the file had them, so a change moves no other line; the save is the engine's, and like the editor's it writes Godot's header and config/features into a file that lacks them. get answers exists, true for any setting the engine knows, and at_default, true when the value is the engine's own default and so has no line in project.godot. Each setting under prefix carries at_default too, so a family read separates what the project sets from the engine's defaults; it is absent for a setting the engine registers no default for, such as an input action the project added, which only the project can have set.",
12384
12409
  parameters: {
12385
12410
  projectPath: PROJECT_PATH,
12386
12411
  setting: {
12387
12412
  type: "string",
12388
- ops: ["get", "set"],
12413
+ ops: ["get", "set", "unset"],
12389
12414
  description: 'Setting path, such as "display/window/size/viewport_width".'
12390
12415
  },
12391
12416
  from: {
@@ -12432,7 +12457,7 @@ var init_tool_definitions = __esm(() => {
12432
12457
  ops: ["add_input_action"],
12433
12458
  minimum: 0,
12434
12459
  maximum: 1,
12435
- description: "Input actions: analogue deadzone, 0 to 1. Default 0.5."
12460
+ description: "Input actions: analogue deadzone, 0 to 1. Default: the deadzone of the action being rebound, or 0.5 for a new one."
12436
12461
  },
12437
12462
  pluginName: {
12438
12463
  type: "string",
@@ -12466,6 +12491,11 @@ var init_tool_definitions = __esm(() => {
12466
12491
  operations: {
12467
12492
  get: { effect: "read", summary: "read one setting, or every setting under prefix", requires: [] },
12468
12493
  set: { effect: "destructive", summary: "write one setting", requires: ["setting", "value"] },
12494
+ unset: {
12495
+ effect: "destructive",
12496
+ summary: "take a setting out of project.godot, so the engine default holds",
12497
+ requires: ["setting"]
12498
+ },
12469
12499
  add_autoload: {
12470
12500
  effect: "additive",
12471
12501
  summary: "register an autoload singleton",
@@ -12661,8 +12691,8 @@ var init_tool_definitions = __esm(() => {
12661
12691
  name: "project_test",
12662
12692
  title: "Run tests",
12663
12693
  effect: "additive",
12664
- description: "Runs the project's gdUnit4 tests headless 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. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it 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. 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. 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 nodes were left in the tree 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.",
12665
- 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. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, with the suite and the case that printed it, and declaredNotPrinted each declared error its case did not print. Either fails the run, and each suite holding one is listed with its count. engineErrors counts every engine error the run printed but those a suite declared. " + IMPORTED_FIRST,
12694
+ description: "Runs the project's gdUnit4 tests headless 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. The class list is rebuilt first, so a suite written a moment ago is found, and the rebuild's answer is under classes, with the classes each addon declares counted under addedInAddons and changedInAddons rather than named: a class it 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. 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. 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.",
12695
+ 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. The run is judged as gdharness test judges it: undeclaredErrors names each engine error that no suite declared in its GDHARNESS_EXPECTED_ERRORS constant, with the suite and the case that printed it, and declaredNotPrinted each declared error its case did not print. A declared text must be the whole error after ERROR: and its space, so a longer error its case printed holding the text is named under printedInstead. Either fails the run, and each suite holding one is listed with its count. engineErrors counts every engine error the run printed but those a suite declared. " + IMPORTED_FIRST,
12666
12696
  parameters: {
12667
12697
  projectPath: PROJECT_PATH,
12668
12698
  path: {
@@ -12704,7 +12734,7 @@ var init_tool_definitions = __esm(() => {
12704
12734
  {
12705
12735
  name: "scene_create",
12706
12736
  title: "Create or save a scene",
12707
- description: `Creates a scene file where none is, saves one, or saves a copy under a new path. create makes a folder the path names that is not there, and names it under madeFolder. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
12737
+ description: `Creates a scene file where none is, saves one, or saves a copy under a new path. create makes a folder the path names that is not there, and names it under madeFolder; its answer names the root's type and name, and under script the script the root carries. A save of a scene open in the editor saves the editor's copy, unsaved changes included. ${WITHOUT_AN_EDITOR}`,
12708
12738
  parameters: {
12709
12739
  projectPath: PROJECT_PATH,
12710
12740
  scenePath: SCENE_PATH,
@@ -12742,7 +12772,7 @@ var init_tool_definitions = __esm(() => {
12742
12772
  name: "scene_tree",
12743
12773
  title: "Scene tree",
12744
12774
  effect: "read",
12745
- description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). A node in any group answers groups, sorted, from this file and every scene it comes from. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
12775
+ description: "The nodes of a scene file: names, classes and hierarchy, with properties when asked. A node that instances another scene names that file under instanceOf, and the nodes listed under it come from that file rather than this one; its editableChildren says whether this scene, or a scene it inherits, keeps values of its own for them (scene_node set editableChildren). The root of a scene inheriting another names the scene it inherits under inherits, and every node that scene or one further down makes names its file under inheritedFrom; this file holds only the values it changes on them. A node in any group answers groups, sorted, from this file and every scene it comes from. Needs no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without making its nodes, so no script of theirs runs (autoloads still start once it has answered, as in every headless call). For the nodes of a game that is running, runtime_inspect tree.",
12746
12776
  parameters: {
12747
12777
  projectPath: PROJECT_PATH,
12748
12778
  scenePath: SCENE_PATH,
@@ -12758,7 +12788,7 @@ var init_tool_definitions = __esm(() => {
12758
12788
  name: "scene_node",
12759
12789
  title: "Scene nodes",
12760
12790
  description: `One node in a scene file: add, read, set, duplicate, reparent or delete it, or paint TileMap cells. Any ClassDB node type or script class can be added, so a NavigationRegion2D, an AnimationTree or a Camera3D is an add with that nodeType and its properties, and another scene is instanced by an add with instancePath. A set with properties {"name": "Boss"} renames a node, 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}`,
12761
- 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. 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. Each of these lists is answered only when it names anything. 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}.",
12791
+ 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. 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}.",
12762
12792
  parameters: {
12763
12793
  projectPath: PROJECT_PATH,
12764
12794
  scenePath: SCENE_PATH,
@@ -12806,7 +12836,7 @@ var init_tool_definitions = __esm(() => {
12806
12836
  keepGlobalTransform: {
12807
12837
  type: "boolean",
12808
12838
  ops: ["reparent"],
12809
- description: "reparent: whether the node stays where it is drawn, its transform worked out again under the new parent, as the editor's reparent and Node.reparent keep it. A Control keeps its position only. Default true; false keeps its own transform, so it moves with the new parent."
12839
+ description: "reparent: whether the node stays where it is drawn, its transform worked out again under the new parent, as the editor's reparent and Node.reparent keep it. A Control keeps its position only. A Node3D kept in place under a parent scaled unevenly across its turn ends up sheared, which no position, rotation and scale describe, and the answer then carries its transform under alsoChanged and says so under sheared. Default true; false keeps its own transform, so it moves with the new parent."
12810
12840
  },
12811
12841
  index: {
12812
12842
  type: "integer",
@@ -12909,18 +12939,18 @@ var init_tool_definitions = __esm(() => {
12909
12939
  {
12910
12940
  name: "scene_animation",
12911
12941
  title: "Animations",
12912
- description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. The player and the tree are nodes, so scene_node add puts one in the scene first. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. add_track on a path the animation already has a track of that type for adds the keys to that track, as the editor keys a property it already animates, answering existingTrack true and keysAdded beside keys, the track's count; a key at a time that track already has a key at is refused. A key past the animation's end, where it never plays, is written and named under pastLength. remove clears the player's autoplay when it named the animation and names under statesPlayingNothing the states that played it. read and read_states need no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without running the scene's scripts. The ops that write go 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. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads 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. An editor of the project that is running and has not reached this server is waited for rather than written around.",
12942
+ description: "Animations in an AnimationPlayer and states in an AnimationTree state machine. The player and the tree are nodes, so scene_node add puts one in the scene first. read answers with every track of an animation in the shape add_track takes, so a track read off one animation can be added to another. add_track on a path the animation already has a track of that type for adds the keys to that track, as the editor keys a property it already animates, answering existingTrack true and keysAdded beside keys, the track's count; a key at a time that track already has a key at is refused. A key past the animation's end, where it never plays, is written and named under pastLength. remove_track takes out every track of the type, nodePath and property given, with its keys. remove clears the player's autoplay when it named the animation and names under statesPlayingNothing the states that played it, which stay in their trees playing nothing until remove_state takes them out. Every state machine has Start and End, which cannot be added or removed; a transition from Start sets the state it begins in. read and read_states need no editor: one serving the project answers and says whether it holds unsaved changes to the scene, and with none a headless engine reads the file without running the scene's scripts. The ops that write go 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. That engine runs every script of the file, where the editor runs only tool scripts, so what a script changes as the file loads 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. An editor of the project that is running and has not reached this server is waited for rather than written around.",
12913
12943
  parameters: {
12914
12944
  projectPath: PROJECT_PATH,
12915
12945
  scenePath: SCENE_PATH,
12916
12946
  playerNodePath: {
12917
12947
  type: "string",
12918
- ops: ["read", "create", "add_track", "set", "rename", "remove"],
12948
+ ops: ["read", "create", "add_track", "remove_track", "set", "rename", "remove"],
12919
12949
  description: "The AnimationPlayer node."
12920
12950
  },
12921
12951
  animationName: {
12922
12952
  type: "string",
12923
- ops: ["read", "create", "add_track", "set", "rename", "remove", "add_state"],
12953
+ ops: ["read", "create", "add_track", "remove_track", "set", "rename", "remove", "add_state"],
12924
12954
  description: "The animation in the player. read: the one to answer with whole, its tracks a list; without it, read lists them, each with the number of its tracks under tracks. add_state: the one that state plays. One the player does not have yet is written all the same, answering animationFound false and why under animationMissing."
12925
12955
  },
12926
12956
  newName: {
@@ -12944,31 +12974,31 @@ var init_tool_definitions = __esm(() => {
12944
12974
  ops: ["create", "set"],
12945
12975
  description: "create and set: keyframe snap in seconds, 0 or more. create: default 0.1."
12946
12976
  },
12947
- track: { ...ANIMATION_TRACK, ops: ["add_track"] },
12977
+ track: { ...ANIMATION_TRACK, ops: ["add_track", "remove_track"] },
12948
12978
  animTreePath: {
12949
12979
  type: "string",
12950
- ops: ["read_states", "add_state", "connect_states"],
12980
+ ops: ["read_states", "add_state", "remove_state", "connect_states", "disconnect_states"],
12951
12981
  description: "The AnimationTree node."
12952
12982
  },
12953
12983
  stateName: {
12954
12984
  type: "string",
12955
- ops: ["add_state"],
12956
- description: "add_state: the state to add. It goes right of the rightmost state in the editor's graph, answered under position."
12985
+ ops: ["add_state", "remove_state"],
12986
+ description: "add_state: the state to add. It goes right of the rightmost state in the editor's graph, answered under position. remove_state: the state to take out, with every transition leaving or reaching it, named under transitionsRemoved."
12957
12987
  },
12958
12988
  stateMachinePath: {
12959
12989
  type: "string",
12960
- ops: ["read_states", "add_state", "connect_states"],
12990
+ ops: ["read_states", "add_state", "remove_state", "connect_states", "disconnect_states"],
12961
12991
  description: "A nested state machine, by the names of the states that lead to it. Default the root."
12962
12992
  },
12963
12993
  fromState: {
12964
12994
  type: "string",
12965
- ops: ["connect_states"],
12966
- description: "connect_states: the state the transition leaves."
12995
+ ops: ["connect_states", "disconnect_states"],
12996
+ description: "connect_states and disconnect_states: the state the transition leaves."
12967
12997
  },
12968
12998
  toState: {
12969
12999
  type: "string",
12970
- ops: ["connect_states"],
12971
- description: "connect_states: the state it arrives at."
13000
+ ops: ["connect_states", "disconnect_states"],
13001
+ description: "connect_states and disconnect_states: the state it arrives at."
12972
13002
  },
12973
13003
  transitionType: {
12974
13004
  type: "string",
@@ -13004,6 +13034,11 @@ var init_tool_definitions = __esm(() => {
13004
13034
  summary: "a property or method track with keyframes",
13005
13035
  requires: ["playerNodePath", "animationName", "track"]
13006
13036
  },
13037
+ remove_track: {
13038
+ effect: "destructive",
13039
+ summary: "a track and its keys from an animation",
13040
+ requires: ["playerNodePath", "animationName", "track"]
13041
+ },
13007
13042
  set: {
13008
13043
  effect: "destructive",
13009
13044
  summary: "an animation's length, loop mode or step",
@@ -13024,10 +13059,20 @@ var init_tool_definitions = __esm(() => {
13024
13059
  summary: "a state playing an animation, in an AnimationTree",
13025
13060
  requires: ["animTreePath", "stateName", "animationName"]
13026
13061
  },
13062
+ remove_state: {
13063
+ effect: "destructive",
13064
+ summary: "a state and its transitions from an AnimationTree",
13065
+ requires: ["animTreePath", "stateName"]
13066
+ },
13027
13067
  connect_states: {
13028
13068
  effect: "additive",
13029
13069
  summary: "a transition between two states",
13030
13070
  requires: ["animTreePath", "fromState", "toState"]
13071
+ },
13072
+ disconnect_states: {
13073
+ effect: "destructive",
13074
+ summary: "the transition from one state to another",
13075
+ requires: ["animTreePath", "fromState", "toState"]
13031
13076
  }
13032
13077
  }
13033
13078
  },
@@ -13091,7 +13136,7 @@ var init_tool_definitions = __esm(() => {
13091
13136
  {
13092
13137
  name: "script_info",
13093
13138
  title: "Script info",
13094
- description: "What a script contains: its structure from the file, or symbols, completions and hover text from the editor's language server.",
13139
+ description: "What a script contains: its structure from the file, or symbols, completions and hover text from the editor's language server. It reads .gd files only, and answers what a script uses rather than what uses it: project_search finds where a class, a member or a signal is named.",
13095
13140
  parameters: {
13096
13141
  projectPath: PROJECT_PATH,
13097
13142
  scriptPath: SCRIPT_PATH,
@@ -13152,7 +13197,7 @@ var init_tool_definitions = __esm(() => {
13152
13197
  {
13153
13198
  name: "resource_edit",
13154
13199
  title: "Edit resources",
13155
- 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 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.',
13200
+ 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". 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.',
13156
13201
  parameters: {
13157
13202
  projectPath: PROJECT_PATH,
13158
13203
  resourcePath: {
@@ -13439,7 +13484,7 @@ var init_tool_definitions = __esm(() => {
13439
13484
  {
13440
13485
  name: "editor_output",
13441
13486
  title: "Game and editor output",
13442
- description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. Answers with the counts and the verdict as well as the entries: clean is true when no error was reported, and warnings, counted beside it, do not make it false. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one measured elsewhere reported a game as playing for fifteen seconds after its process had been ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, which is where the output worth reading usually is, since the class cache, the plugins and the language server all say what they have to say before any harness has connected. The answer carries the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
13487
+ description: "What the project started by editor_run has printed, as entries with a severity: the errors and warnings the engine reported, each with where it happened, and everything else as info. For a run the editor plays the reports come from the game itself, through the runtime addon, since the editor's debugger relays what a game prints and not what it reports; a played game without the runtime addon answers here with its prints alone, with errorsUnread true and no clean, since what it raised reaches nothing here. A run answers with counts and a verdict: clean is true when no error was reported, and warnings, counted beside it, do not make it false. heldAt is three answers: null for a run this server knows is not held, running or over, an object for one it knows is held at a breakpoint, whether the adapter said so or the runtime did by accepting a connection and never answering, and absent with heldUnknown beside it for a server that connected to the debugger after the stop and has no runtime to ask, since the adapter reports a stop only to the sessions connected when it happened. A run that has quit still answers here, with running false and its exit code, until the next one starts; a run ended by a signal has no exit code, and exitSignal names the signal instead. A run this server started also answers with transcript, the file both its streams are written to: uncapped, written while the run is going, and the thing to read or tail for a long run rather than the engine's own log, which every engine start rotates away. Read the file for output and this answer for state: endedBy says whether gdharness ended the run, and a transcript that has stopped growing is a run between prints rather than a run that is over. running is asked of the operating system whenever there is a process to ask about, which is every run this server started and every editor-played run whose game announced its runtime. For an editor-played run that never announced, there is no process id here and running is the editor's answer, which can lag: one reported a game as playing fifteen seconds after its process was ended from outside. editor_status names that game under runtimes, which does not depend on the editor answering. op: \"editor\" answers about the editor itself instead, which is a different log and the only way anything here can read what an editor printed: its console reaches no plugin, so the addon cannot scrape it, and an editor gdharness opened is asked for a log file as it starts. That covers the whole session from the editor's first line, where the output worth reading usually is, since the class cache, the plugins and the language server speak before any harness connects. It answers counts with no clean, since an editor neither passes nor fails, the entries in the order they were printed and, beside them under repeated, each message shape several lines share: how many, what varied in each slot of it and how many different values stood there, the first occurrence, and the ordinary lines above the group, found by walking back past the group rather than by counting lines. An editor somebody opened by hand has no console here and is refused saying so rather than answered empty, because an editor that printed nothing and an editor nobody captured are not the same thing. startedAt is when the run began and elapsedMs how long it has been going; for a run that is over, endedAt is when it ended and elapsedMs how long it ran, and both are left out when nobody saw the end, as for a game closed in the editor or a run picked up from a note written before ends were recorded.",
13443
13488
  answer: "Each entry's source says where it was read: transcript for a line of the run's transcript file, which holds both its streams and is how every run with one is read; stdout or stderr for a line of that stream of a run with no transcript, such as an editor-played run picked up after a reconnect whose first server left no note; debugger for what the editor's debug adapter reported and the game never printed, such as the script error a game stopped on.",
13444
13489
  parameters: {
13445
13490
  projectPath: {
@@ -13505,7 +13550,7 @@ var init_tool_definitions = __esm(() => {
13505
13550
  name: "editor_rescan",
13506
13551
  title: "Rescan the project",
13507
13552
  effect: "additive",
13508
- description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it, and the answer says how long under waitedForEditorScanMs. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
13553
+ description: "Makes the running editor scan the project filesystem, so files written outside it are picked up. A scan or an import the editor is already running is waited out first rather than scanned over, which would start a second import over it. waitedMs is the whole call, and waitedForEditorScanMs, given only when a scan or an import was already running, the part spent waiting it out. The scan is a change-detecting walk rather than an unconditional reparse, so a file another engine has already imported reads as settled and the walk does not look inside it: its class_name then stays out of the list the editor resolves against, however many times you scan. Any class in that state is named under unseenByEditor, which is what no check on disk can see, since the declaration and the cache are both correct there and only the editor disagrees. The cure is this call on its own, with no change to the declaring script: measured against a real editor on three platforms, with idle waits of the same length ruled out so the scan is credited rather than the time it takes, and reproduced in a second project against its own reproduction in 203ms. editor_launch restart also does it and costs a window, and project_import refresh_classes does not: it rewrites the cache and does not touch what the editor is holding. The walk also passes over a file whose .uid alone changed, so the editor keeps the old UID and every engine reading its cache warns of a duplicate; before scanning, each file whose .uid names a UID the editor does not hold for it is read again and named under uidsReread, and files on disk naming one UID between them are named under uidsDuplicatedOnDisk, which reading again cannot settle. A class the scan brings in is named under broughtIn, and every script naming it is reloaded in the editor and named under dependentsReloaded: what the editor compiled from them while it could not see the class stays compiled that way after the scan, and a script extending a new base class went on reporting \"Could not find base class\" until reloaded. One that would not reload is named under dependentsNotReloaded and the call is not ok, as it is not when the script named in reloadScript was not reloaded. An editor importing answers nothing until the import is over, and a hidden one imports on the thread that answers, so a large addon install can leave the rescan itself unanswered: that is waited for as any busy editor is, within timeoutMs, and when the wait runs out first the answer is stillWorking. The classes the editor could not see before that scan are then noted in the project, and the next editor_rescan of the same editor, from any server, names them under broughtIn and reloads what names them, which it could not otherwise know to do, since by then the editor sees them. A scan writes .godot/global_script_class_cache.cfg from the list the editor is holding, so a class the editor cannot resolve goes out of that file with it. That is not a reason to refuse the scan, because on a class the editor has merely not walked yet the scan is the cure; what the answer does instead is name the loss under cacheLost and rebuild the cache from the files, naming what came back under cacheRestored, so no fresh engine, CI run or clone inherits the short file. An editor that lost classes is still holding the short list, so the note says to restart it before scanning again. The other direction is guarded the same way: a class the editor still holds for a script that was renamed or deleted comes back into the cache at a path that is not there, and the next engine to read it fails on \"Could not parse global class\" in whichever correct script shares the bare name, so the cache is rebuilt without it and the answer names it under cacheDropped; the editor goes on holding it until it is restarted, and every scan until then ends in that rebuild. A script or shader written outside the editor has no .uid sidecar until something imports it, and the scan writes one beside it, which a project that commits sidecars needs before the commit. Needs the editor connected.",
13509
13554
  parameters: {
13510
13555
  projectPath: PROJECT_PATH,
13511
13556
  timeoutMs: {
@@ -13667,7 +13712,7 @@ var init_tool_definitions = __esm(() => {
13667
13712
  tree: { effect: "read", summary: "the live scene tree", requires: [] },
13668
13713
  text: {
13669
13714
  effect: "read",
13670
- summary: "every line of text under nodePath, the values in its fields included, in reading order, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without its tags, text only as far as it has been typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items and the titles of its separators read a line each",
13715
+ summary: "every line of text under nodePath, the values in its fields included, in reading order, leaving out what is hidden and everything under it. Every line reads as drawn: rich text without tags, text only as far as it is typed out, a translation rather than its key, an upper-case label in capitals and a secret field as its mask. Tab titles, menu bar titles, list items, a tree's column titles and rows, and an open menu's items and the titles of its separators read a line each",
13671
13716
  requires: []
13672
13717
  },
13673
13718
  find: {
@@ -13687,7 +13732,7 @@ var init_tool_definitions = __esm(() => {
13687
13732
  },
13688
13733
  metrics: {
13689
13734
  effect: "read",
13690
- summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of a frame over the last frames_timed frames (up to 120, timed by the game, physics and the wait for the display included), frame_time_max the slowest of them, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. physics_steps_per_second is the physics steps taken a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, which it sets once a second: the frames counted in the last second, and the slowest process and physics step in it, so they trail a change by up to a second and the last two are a worst case rather than the frame. Memory, object and render counts are read as asked. memory_static is what the engine allocated on the CPU side, in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are what the renderer has on the GPU, in bytes, which memory_static does not count",
13735
+ summary: "frame time, memory, draw calls and the rest. frame_time is the mean wall time of the last frames_timed frames (up to 120; the game, physics and the wait for the display included), frame_time_max the slowest, both in seconds and null before a frame has passed; vsync says whether a frame waits for the display, and max_fps is the game's frame-rate cap, 0 for none. process_frames and physics_frames count frames and physics steps since the start; get_frames_drawn() stays 0 headless. physics_steps_per_second is the physics steps a second over those frames, fewer than physics_ticks_per_second when a frame needs more than max_physics_steps_per_frame or the editor's Game view slows it. fps, process_time_max and physics_time are the engine's monitors, set once a second: the last second's frames and its slowest process and physics step, so they trail a change by up to a second, and the last two are worst cases, not the frame. Memory, object and render counts are read as asked. memory_static is the engine's CPU-side allocation in bytes; render_video_mem_used, render_texture_mem_used and render_buffer_mem_used are the renderer's GPU memory in bytes, which memory_static leaves out",
13691
13736
  requires: []
13692
13737
  },
13693
13738
  accessibility: {
@@ -13701,7 +13746,7 @@ var init_tool_definitions = __esm(() => {
13701
13746
  {
13702
13747
  name: "runtime_invoke",
13703
13748
  title: "Set or call in the running game",
13704
- description: "Sets a property or calls a method on a node, or on one of the engine singletons nodePath names, in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. A method that awaits is answered with the value it returns once it ends, and awaited true; elapsed_usec then runs to its end, and one that outlasts timeout is collected with op result. Errors the engine reported while a called method ran come back under errors and warnings under warnings, the first 20 of each, with errors_not_shown and warnings_not_shown counting the rest. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes.",
13749
+ description: 'Sets a property or calls a method on a node, or on one of the engine singletons nodePath names, in the running game. Needs the game running with the runtime addon. The answer carries elapsed_usec, how long the call or the write took inside the game, measured around it there: the round trip to the game is a second or more, so timing from outside measures that instead. A method that awaits is answered with the value it returns once it ends, and awaited true; elapsed_usec then runs to its end, and one that outlasts timeout is collected with op result. Errors the engine reported while a called method ran come back under errors and warnings under warnings, the first 20 of each, with errors_not_shown and warnings_not_shown counting the rest. A script error ends the function it is raised in, which then returns the default of its return type, 0, "" or null, so one that ended the called method itself is named under ended_by_error_at, and result is not a value the method returned. A call that takes longer than timeoutMs is not cancelled: the answer is pending: true with a requestId, the call goes on and does everything it was asked, and op result with that requestId collects its reply once it comes. A game this server started that its debugger holds is said so, with where, before a call and under heldAt when a call reached a breakpoint.',
13705
13750
  parameters: {
13706
13751
  projectPath: RUNNING_PROJECT_PATH,
13707
13752
  pid: RUNNING_PID,
@@ -13826,7 +13871,7 @@ var init_tool_definitions = __esm(() => {
13826
13871
  name: "runtime_input",
13827
13872
  title: "Send input to the game",
13828
13873
  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.",
13829
- 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. None of these is aimed at anything, so none says what the event reached: click on a nodePath or says answers what was under the pointer. An action or a key 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. 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.`,
13874
+ 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 and click answer actions, the InputMap actions the event also presses. 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. An action or a key 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. 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.`,
13830
13875
  parameters: {
13831
13876
  projectPath: RUNNING_PROJECT_PATH,
13832
13877
  pid: RUNNING_PID,
@@ -13843,7 +13888,7 @@ var init_tool_definitions = __esm(() => {
13843
13888
  action: {
13844
13889
  type: "string",
13845
13890
  ops: ["action"],
13846
- description: "action: the InputMap action name. An engine dialog is not answered this way: AcceptDialog reads the Escape key itself and never asks the InputMap, so ui_cancel goes in and the question stays up. Dismiss one with the key op and keycode Escape, or click its button."
13891
+ description: "action: the InputMap action name. A Viewport never hands an action to a shortcut, so the buttons whose Shortcut holds the action are sent it as their Shortcut too, and pressed as by the key bound to it: one button at most, the last in the tree that takes it, passing over one that is disabled, hidden or outside its shortcut context. The answer names those buttons under shortcuts and the one pressed under shortcut_pressed, null when none took it. An engine dialog is not answered this way: AcceptDialog reads the Escape key itself and never asks the InputMap, so ui_cancel goes in and the question stays up. Dismiss one with the key op and keycode Escape, or click its button."
13847
13892
  },
13848
13893
  pressed: {
13849
13894
  type: "boolean",
@@ -14108,7 +14153,7 @@ var init_tool_definitions = __esm(() => {
14108
14153
  {
14109
14154
  name: "debug_control",
14110
14155
  title: "Step the debugger",
14111
- description: "Pauses, continues or steps the debugged game. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. A game the editor plays is moved through the editor's debug adapter, which cannot pause or step out: it answers a pause by reporting the game stopped and leaving it running, and implements no stepOut, so hold such a game with a breakpoint and step over or into from there. A game this server started itself has its keeper as its debugger, which does both: pause breaks into the game wherever it is, a script spinning in a loop included, which is how a game hung on its first frame is asked where it is.",
14156
+ description: "Pauses, continues or steps the debugged game. A step waits for the game to stop again and answers with the stack where it ended up; one that has not stopped the game again within ten seconds answers heldAgain false and no stack. A game the editor plays is moved through the editor's debug adapter, which cannot pause or step out: it answers a pause by reporting the game stopped and leaving it running, and implements no stepOut, so hold such a game with a breakpoint and step over or into from there. A game this server started itself has its keeper as its debugger, which does both: pause breaks into the game wherever it is, a script spinning in a loop included, which is how a game hung on its first frame is asked where it is. For such a game a step that ends in gdharness's runtime addon, as one past a function's last line does when the addon's _process runs next, is stepped on to the game's own script as a pause is; and since the engine reports every stop the same way, a step that stops on a line holding a breakpoint says onBreakpoint under held, whether the step ended there or ran into it.",
14112
14157
  parameters: {},
14113
14158
  requires: [],
14114
14159
  operations: {
@@ -14165,6 +14210,7 @@ var init_headless_operations = __esm(() => {
14165
14210
  project_settings: dictionary({
14166
14211
  get: "get_project_setting",
14167
14212
  set: "set_project_setting",
14213
+ unset: "unset_project_setting",
14168
14214
  add_autoload: "add_autoload",
14169
14215
  remove_autoload: "remove_autoload",
14170
14216
  set_main_scene: "set_main_scene",
@@ -14751,6 +14797,13 @@ function readRun(printed) {
14751
14797
  const unloadable = scriptErrorsPrinted(lines).map((error) => `${error.path}${error.line === null ? "" : `:${String(error.line)}`}: ${error.message}`);
14752
14798
  return { lines, suites, errors, leaks, starved, executed, unloadable, stoppedIn };
14753
14799
  }
14800
+ function withNestedErrors(printed, piped) {
14801
+ const nested = piped.flatMap((entry, at) => {
14802
+ const next = piped[at + 1];
14803
+ return entry.severity === "info" && entry.text.trim() === NESTED_ERROR && next?.severity === "error" ? [{ text: next.text, at: printed.lines.length, suite: null, during: null }] : [];
14804
+ });
14805
+ return nested.length === 0 ? printed : { ...printed, errors: [...printed.errors, ...nested] };
14806
+ }
14754
14807
  function readExpected(file) {
14755
14808
  try {
14756
14809
  const parsed = JSON.parse(readFileSync16(file, "utf8"));
@@ -14921,7 +14974,7 @@ ${error.during}`);
14921
14974
  const neverPrinted = [...owed.values()].flatMap((debt) => debt.texts.map((text) => ({ suite: debt.suite, during: debt.during, text })));
14922
14975
  return { printed: unexpected, neverPrinted };
14923
14976
  }
14924
- var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, EXECUTED, DRIVER_FALLBACK, LEAK, STARVED, WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
14977
+ var SUITE_OPENED, TIME, CASE_LINE, STATISTICS, ERROR_LINE, EXECUTED, DRIVER_FALLBACK, LEAK, STARVED, NESTED_ERROR = "While attempting to print an error, another error was printed:", WINDOWS_EXITS, MEMORY_EXITS, GDUNIT_FAILED = 100, GDUNIT_EXITS, TAIL = 60, MOST_NAMED = 8;
14925
14978
  var init_suite_judge = __esm(() => {
14926
14979
  init_junit();
14927
14980
  SUITE_OPENED = /^Run Test Suite:\s*(res:\/\/.+?\.gd)\s*$/;
@@ -46733,6 +46786,7 @@ function settingsNamedBy(op, args) {
46733
46786
  const text = (key) => readString(args, key) ?? "";
46734
46787
  switch (op) {
46735
46788
  case "set":
46789
+ case "unset":
46736
46790
  return text("setting") === "" ? [] : [text("setting")];
46737
46791
  case "add_autoload":
46738
46792
  case "remove_autoload":
@@ -47235,7 +47289,7 @@ function scriptsToDiagnose(projectPath, given) {
47235
47289
  found.push(relative);
47236
47290
  continue;
47237
47291
  }
47238
- const inside = scriptsUnder(location.absolutePath, relative);
47292
+ const inside = folderFiles(location.absolutePath, [".gd"]).map((script) => relative === "" ? script : `${relative}/${script}`);
47239
47293
  if (inside.length === 0) {
47240
47294
  empty.push(entry);
47241
47295
  }
@@ -47251,7 +47305,7 @@ function engineChecked(why) {
47251
47305
  return {
47252
47306
  checkedBy: "engine",
47253
47307
  languageServer: why,
47254
- note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, and no warnings, which the engine tells only an editor: warnings is null rather than 0. editor_launch with hidden: true opens this project's editor without a window, and its language server reports both."
47308
+ note: "No editor of this project is open to ask, so a headless engine compiled the scripts the way loading them does. It reports every error that stops a script compiling, at its line and without a column, and no warnings, which the engine tells only an editor: warnings is null rather than 0. editor_launch with hidden: true opens this project's editor without a window, and its language server reports both. Until then clean says only that every script compiles."
47255
47309
  };
47256
47310
  }
47257
47311
  function isDirectory(path) {
@@ -47261,24 +47315,6 @@ function isDirectory(path) {
47261
47315
  return false;
47262
47316
  }
47263
47317
  }
47264
- function scriptsUnder(directory, prefix) {
47265
- if (existsSync30(join38(directory, ".gdignore"))) {
47266
- return [];
47267
- }
47268
- const found = [];
47269
- for (const entry of readdirSync17(directory, { withFileTypes: true }).sort((a, b) => a.name < b.name ? -1 : 1)) {
47270
- if (entry.name.startsWith(".")) {
47271
- continue;
47272
- }
47273
- const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
47274
- if (entry.isDirectory()) {
47275
- found.push(...scriptsUnder(join38(directory, entry.name), path));
47276
- } else if (entry.isFile() && entry.name.endsWith(".gd")) {
47277
- found.push(path);
47278
- }
47279
- }
47280
- return found;
47281
- }
47282
47318
  function mainSceneOf(projectFile) {
47283
47319
  const scene = parseProjectGodot(readFileSync34(projectFile, "utf8"))["application"]?.["run/main_scene"];
47284
47320
  return typeof scene === "string" && scene !== "" ? scene : null;
@@ -48115,6 +48151,8 @@ class GodotServer {
48115
48151
  return await editorOrEngine("create_animation", "writes");
48116
48152
  case "add_track":
48117
48153
  return await editorOrEngine("add_animation_track", "writes");
48154
+ case "remove_track":
48155
+ return await editorOrEngine("remove_animation_track", "writes");
48118
48156
  case "set":
48119
48157
  return await editorOrEngine("set_animation", "writes");
48120
48158
  case "rename":
@@ -48123,6 +48161,10 @@ class GodotServer {
48123
48161
  return await editorOrEngine("remove_animation", "writes");
48124
48162
  case "add_state":
48125
48163
  return await editorOrEngine("add_animation_state", "writes");
48164
+ case "remove_state":
48165
+ return await editorOrEngine("remove_animation_state", "writes");
48166
+ case "disconnect_states":
48167
+ return await editorOrEngine("disconnect_animation_states", "writes");
48126
48168
  default:
48127
48169
  return await editorOrEngine("connect_animation_states", "writes");
48128
48170
  }
@@ -49467,7 +49509,7 @@ class GodotServer {
49467
49509
  const reportsDir = join38(project.value.path, ".godot", ...ours.split("/"));
49468
49510
  let report = null;
49469
49511
  let reportProblem = null;
49470
- const casePrinted = readRun(existsSync30(engineLog) ? readFileSync34(engineLog, "utf8") : "");
49512
+ const casePrinted = withNestedErrors(readRun(existsSync30(engineLog) ? readFileSync34(engineLog, "utf8") : ""), run.log.all);
49471
49513
  const declaredErrors = readExpected(expectedFile);
49472
49514
  try {
49473
49515
  const written = existsSync30(reportsDir) && notCompiled === null ? readdirSync17(reportsDir).filter((name) => name.startsWith("report_")).sort((a, b) => Number(a.slice("report_".length)) - Number(b.slice("report_".length))) : [];
@@ -49582,7 +49624,7 @@ class GodotServer {
49582
49624
  path: suite.path,
49583
49625
  name: report.suites.find((entry) => entry.path === suite.path)?.name ?? suite.path,
49584
49626
  orphans: suite.orphans,
49585
- message: `${suite.orphans} node${suite.orphans === 1 ? "" : "s"} were still in the tree when this suite finished. A node built in a test and neither freed nor added to the tree is the usual cause; free it, or add it with auto_free().`
49627
+ message: `${suite.orphans === 1 ? "1 node" : `${String(suite.orphans)} nodes`} built during this suite ${suite.orphans === 1 ? "was" : "were"} neither freed nor in the tree when it finished, which gdUnit4 counts as orphans. Free each one, or make it with auto_free(), which frees it once the case is over.`
49586
49628
  }));
49587
49629
  const judgedErrors = unexpectedErrors(casePrinted, declaredErrors, [...casePrinted.suites.keys()]);
49588
49630
  const uncaughtTexts = new Set(uncaught.map((entry) => entry.text));
@@ -49612,7 +49654,8 @@ ${error.text}`;
49612
49654
  const declaredNotPrinted = judgedErrors.neverPrinted.map((missing) => ({
49613
49655
  suite: missing.suite,
49614
49656
  case: missing.during,
49615
- text: missing.text
49657
+ text: missing.text,
49658
+ printedInstead: judgedErrors.printed.find((error) => error.suite === missing.suite && error.during === missing.during && error.text.includes(missing.text))?.text
49616
49659
  }));
49617
49660
  const erring = (path) => ({
49618
49661
  undeclared: undeclaredPrinted.filter((error) => error.suite === path).length,
@@ -53443,6 +53486,24 @@ ${error.text}`;
53443
53486
  }
53444
53487
  return session.whereItStopped();
53445
53488
  }
53489
+ async keptHoldOn(endpoint) {
53490
+ const kept = this.keptRun();
53491
+ if (kept === null || this.announcedPidOf(kept) !== endpoint.pid) {
53492
+ return null;
53493
+ }
53494
+ const answered = await askTheKeepersDebugger(kept.keeper, { op: "stack" }, KEEPER_DEBUG_MS);
53495
+ if ("unreached" in answered || !Array.isArray(answered["stack"])) {
53496
+ return null;
53497
+ }
53498
+ const top = answered["stack"][0];
53499
+ const held = answered["held"];
53500
+ const reason = typeof held === "object" && held !== null && typeof held["reason"] === "string" ? ` (${String(held["reason"])})` : "";
53501
+ if (typeof top !== "object" || top === null) {
53502
+ return `held by its debugger between frames${reason}`;
53503
+ }
53504
+ const frame = top;
53505
+ return `held by its debugger at ${String(frame["script"])}:${String(frame["line"])} in ${String(frame["name"])}${reason}`;
53506
+ }
53446
53507
  async holdOf(run, going) {
53447
53508
  if (!run.throughEditor || !(going ?? await this.runStillGoing(run))) {
53448
53509
  return { heldAt: null };
@@ -53558,6 +53619,14 @@ ${error.text}`;
53558
53619
  "editor_output says heldAt for the run, and editor_run stop ends it"
53559
53620
  ]);
53560
53621
  }
53622
+ const keptHeld = await this.keptHoldOn(choice.endpoint);
53623
+ if (keptHeld !== null) {
53624
+ return this.createErrorResponse(`The game (pid ${choice.endpoint.pid}) is ${keptHeld}, and answers no runtime call while it is.`, [
53625
+ "debug_control continue lets it go, after which this call answers",
53626
+ "debug_state stack reads where it is held, and debug_state variables what it is holding there",
53627
+ "editor_run stop ends it"
53628
+ ]);
53629
+ }
53561
53630
  const expectsScreenshot = command === "capture_screenshot" || command === "capture_viewport";
53562
53631
  const saveTo = expectsScreenshot && typeof outputPath === "string" ? outputPath : null;
53563
53632
  const sendsThePicture = readBoolean(callArgs, "inline") !== false;
@@ -53601,12 +53670,14 @@ ${error.text}`;
53601
53670
  const reply = await runtimeRequest(choice.endpoint, command, screenshotPath ? { ...params, output_path: screenshotPath } : params, timeoutMs, expectsScreenshot ? () => discard(screenshotDir) : "keep");
53602
53671
  savedLater = !reply.ok && reply.stillWithTheGame === true;
53603
53672
  if (!reply.ok && reply.requestId !== undefined) {
53673
+ const heldSince = await this.keptHoldOn(choice.endpoint);
53604
53674
  return this.jsonTextResponse({
53605
53675
  pending: true,
53606
53676
  requestId: reply.requestId,
53607
53677
  command,
53608
53678
  waitedMs: timeoutMs,
53609
- note: `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}`,
53679
+ ...heldSince === null ? {} : { heldAt: heldSince },
53680
+ note: heldSince === null ? `${reply.message} ${longerWaitNote(asked.tool, asked.op, timeoutMs)}` : `The game is ${heldSince}, so it answers once it goes on: debug_control continue lets it go, and runtime_invoke op result with requestId ${reply.requestId} then answers with what the call returned.`,
53610
53681
  ...answeredBy,
53611
53682
  ...alongside()
53612
53683
  });