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.
- package/build/cli.js +137 -66
- package/build/godot/addons/gdharness_editor/file_reads.gd +13 -0
- package/build/godot/addons/gdharness_editor/reading.gd +61 -5
- package/build/godot/addons/gdharness_editor/scene_file.gd +41 -35
- package/build/godot/addons/gdharness_editor/serialisation.gd +8 -7
- package/build/godot/addons/gdharness_editor/tool_executor.gd +12 -2
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd +244 -0
- package/build/godot/addons/gdharness_editor/tools/animation_states.gd.uid +1 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +105 -157
- package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +25 -1
- package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +4 -1
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +10 -0
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +72 -3
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +7 -1
- package/build/godot/addons/gdharness_runtime/call_errors.gd +8 -2
- package/build/godot/addons/gdharness_runtime/reading.gd +61 -5
- package/build/godot/addons/gdharness_runtime/runtime_capture.gd +5 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +3 -0
- package/build/godot/addons/gdharness_runtime/runtime_input.gd +27 -31
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +11 -5
- package/build/godot/addons/gdharness_runtime/runtime_queries.gd +2 -0
- package/build/godot/addons/gdharness_runtime/runtime_targets.gd +26 -0
- package/build/godot/addons/gdharness_runtime/runtime_values.gd +105 -6
- package/build/godot/addons/gdharness_runtime/serialisation.gd +8 -7
- package/build/godot/operations/editor_tools.gd +8 -0
- package/build/godot/operations/file_reads.gd +13 -0
- package/build/godot/operations/gdscript_analysis.gd +14 -0
- package/build/godot/operations/gdscript_authoring.gd +1 -1
- package/build/godot/operations/godot_operations.gd +2 -0
- package/build/godot/operations/input_actions.gd +22 -7
- package/build/godot/operations/project_config.gd +31 -0
- package/build/godot/operations/reading.gd +61 -5
- package/build/godot/operations/serialisation.gd +8 -7
- package/build/keeper.js +43 -13
- 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
|
-
|
|
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(
|
|
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: "
|
|
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: {
|
|
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"
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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.
|
|
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 =
|
|
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}
|
|
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
|
-
|
|
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
|
});
|