gdharness 1.3.45 → 1.3.46
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 +291 -191
- package/build/godot/addons/gdharness_editor/animation_reads.gd +8 -0
- package/build/godot/addons/gdharness_editor/scene_file.gd +190 -0
- package/build/godot/addons/gdharness_editor/tool_executor.gd +4 -0
- package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +89 -3
- package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +61 -0
- package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +21 -2
- package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +18 -5
- package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +22 -3
- package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +7 -0
- package/build/godot/addons/gdharness_runtime/runtime_changes.gd +9 -0
- package/build/godot/addons/gdharness_runtime/runtime_paths.gd +29 -2
- package/build/godot/operations/animation_reads.gd +8 -0
- package/build/godot/operations/editor_tools.gd +2 -0
- package/build/godot/operations/patterns.gd +27 -2
- package/build/godot/operations/project_diagnostics.gd +2 -1
- package/build/godot/operations/project_validation.gd +4 -2
- package/build/keeper.js +3 -2
- package/package.json +1 -1
|
@@ -503,7 +503,7 @@ static func can_read(holder: Variant, named: String) -> bool:
|
|
|
503
503
|
var object: Object = holder
|
|
504
504
|
if not method.is_empty():
|
|
505
505
|
return call_refused(object, method, "", args.size()).is_empty()
|
|
506
|
-
return _has_property(object, named)
|
|
506
|
+
return _has_property(object, named) or not constant_of(object, named).is_empty()
|
|
507
507
|
return false
|
|
508
508
|
|
|
509
509
|
|
|
@@ -876,7 +876,7 @@ static func read_under(holder: Variant, named: String) -> Variant:
|
|
|
876
876
|
if not method.is_empty():
|
|
877
877
|
var args: Array = arguments_of(named)["args"]
|
|
878
878
|
return object.callv(method, args)
|
|
879
|
-
return object.get(named)
|
|
879
|
+
return constant_of(object, named)[0] if is_constant(object, named) else object.get(named)
|
|
880
880
|
|
|
881
881
|
|
|
882
882
|
## Where [param named] lands in a list of [param size], or -1 for a step that is not in it.
|
|
@@ -1030,3 +1030,30 @@ static func _has_property(holder: Object, named: String) -> bool:
|
|
|
1030
1030
|
if str(entry["name"]) == named:
|
|
1031
1031
|
return true
|
|
1032
1032
|
return false
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
## The constant [param named] that [param holder]'s script or a script it extends declares, in a
|
|
1036
|
+
## list of one, or an empty list. A constant is no property, so the property list never names it,
|
|
1037
|
+
## and `get` answers null for one.
|
|
1038
|
+
static func constant_of(holder: Variant, named: String) -> Array:
|
|
1039
|
+
if not holder is Object:
|
|
1040
|
+
return []
|
|
1041
|
+
var object: Object = holder
|
|
1042
|
+
var attached: Variant = object.get_script()
|
|
1043
|
+
if not attached is Script:
|
|
1044
|
+
return []
|
|
1045
|
+
var script: Script = attached
|
|
1046
|
+
while script != null:
|
|
1047
|
+
var constants: Dictionary = script.get_script_constant_map()
|
|
1048
|
+
if constants.has(named):
|
|
1049
|
+
return [constants[named]]
|
|
1050
|
+
script = script.get_base_script()
|
|
1051
|
+
return []
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
## Whether [param named] reaches a constant on [param holder] rather than a property.
|
|
1055
|
+
static func is_constant(holder: Variant, named: String) -> bool:
|
|
1056
|
+
if not holder is Object:
|
|
1057
|
+
return false
|
|
1058
|
+
var object: Object = holder
|
|
1059
|
+
return not _has_property(object, named) and not constant_of(object, named).is_empty()
|
|
@@ -11,6 +11,11 @@ const LOOP_MODE_NAMES: Dictionary[int, String] = {
|
|
|
11
11
|
Animation.LOOP_LINEAR: "linear",
|
|
12
12
|
Animation.LOOP_PINGPONG: "pingpong",
|
|
13
13
|
}
|
|
14
|
+
const UPDATE_MODE_NAMES: Dictionary[int, String] = {
|
|
15
|
+
Animation.UPDATE_CONTINUOUS: "continuous",
|
|
16
|
+
Animation.UPDATE_DISCRETE: "discrete",
|
|
17
|
+
Animation.UPDATE_CAPTURE: "capture",
|
|
18
|
+
}
|
|
14
19
|
const TRACK_TYPE_NAMES: Dictionary[int, String] = {
|
|
15
20
|
Animation.TYPE_VALUE: "property",
|
|
16
21
|
Animation.TYPE_POSITION_3D: "position_3d",
|
|
@@ -166,6 +171,9 @@ static func _track(anim: Animation, track: int, values: GdhSerialisation) -> Dic
|
|
|
166
171
|
var property: String = str(path.get_concatenated_subnames())
|
|
167
172
|
if not property.is_empty():
|
|
168
173
|
shaped["property"] = property
|
|
174
|
+
if kind == Animation.TYPE_VALUE:
|
|
175
|
+
var mode: int = anim.value_track_get_update_mode(track)
|
|
176
|
+
shaped["update"] = UPDATE_MODE_NAMES.get(mode, str(mode))
|
|
169
177
|
var keyframes: Array[Dictionary] = []
|
|
170
178
|
var methods: Dictionary = {}
|
|
171
179
|
for key: int in anim.track_get_key_count(track):
|
|
@@ -81,6 +81,8 @@ static func run(params: Dictionary) -> Dictionary:
|
|
|
81
81
|
var answered: Dictionary = answer
|
|
82
82
|
if not GdhSceneFile.also_saved.is_empty():
|
|
83
83
|
answered["alsoSaved"] = GdhSceneFile.also_saved.duplicate(true)
|
|
84
|
+
if not GdhSceneFile.unmade_note.is_empty():
|
|
85
|
+
answered["unmadeConnections"] = GdhSceneFile.unmade_note
|
|
84
86
|
if not GdhSceneUids.by_path.is_empty():
|
|
85
87
|
answered["namedByPath"] = (
|
|
86
88
|
(
|
|
@@ -96,6 +96,11 @@ static func without_annotations(line: String) -> String:
|
|
|
96
96
|
# brace opens before the `#` and closes on a later line. Held by a case, since the argument for the
|
|
97
97
|
# difference is not visible from the code and reads like an oversight beside its neighbours.
|
|
98
98
|
static func without_comment(line: String) -> String:
|
|
99
|
+
var start: int = _comment_start(line)
|
|
100
|
+
return (line if start == -1 else line.substr(0, start)).strip_edges()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
static func _comment_start(line: String) -> int:
|
|
99
104
|
var quote: String = ""
|
|
100
105
|
for i: int in range(line.length()):
|
|
101
106
|
var ch: String = line[i]
|
|
@@ -104,10 +109,30 @@ static func without_comment(line: String) -> String:
|
|
|
104
109
|
quote = ""
|
|
105
110
|
continue
|
|
106
111
|
if ch == "#":
|
|
107
|
-
return
|
|
112
|
+
return i
|
|
108
113
|
if ch == '"' or ch == "'":
|
|
109
114
|
quote = ch
|
|
110
|
-
return
|
|
115
|
+
return -1
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## The code in front of each TODO or FIXME comment in [param content], one entry a comment.
|
|
119
|
+
##
|
|
120
|
+
## Read from the comments alone, as the editor highlights them, so `#TODO` counts and the same
|
|
121
|
+
## words inside a string or a multi-line string do not.
|
|
122
|
+
static func noted_work(content: String) -> Array[String]:
|
|
123
|
+
var noted: Array[String] = []
|
|
124
|
+
var in_string: bool = false
|
|
125
|
+
for line: String in content.split("\n"):
|
|
126
|
+
if in_string:
|
|
127
|
+
in_string = triple_quotes(line) % 2 == 0
|
|
128
|
+
continue
|
|
129
|
+
var start: int = _comment_start(line)
|
|
130
|
+
var code: String = line if start == -1 else line.substr(0, start)
|
|
131
|
+
if triple_quotes(code) % 2 == 1:
|
|
132
|
+
in_string = true
|
|
133
|
+
elif start != -1 and compiled("^#+\\s*(?:TODO|FIXME)\\b").search(line.substr(start)):
|
|
134
|
+
noted.append(code.strip_edges())
|
|
135
|
+
return noted
|
|
111
136
|
|
|
112
137
|
|
|
113
138
|
## How many triple quotes of either kind [param line] holds. An odd count opens or closes a string
|
|
@@ -5,6 +5,7 @@ const GdhFileWalk = preload("file_walk.gd")
|
|
|
5
5
|
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
6
6
|
const GdhInputActions = preload("input_actions.gd")
|
|
7
7
|
const GdhLog = preload("logger.gd")
|
|
8
|
+
const GdhPatterns = preload("patterns.gd")
|
|
8
9
|
const GdhRead = preload("reading.gd")
|
|
9
10
|
const GdhSceneConnections = preload("scene_connections.gd")
|
|
10
11
|
const GdhScriptCheck = preload("script_check.gd")
|
|
@@ -87,7 +88,7 @@ func get_project_health(params: Dictionary) -> Dictionary:
|
|
|
87
88
|
var content: String = file.get_as_text()
|
|
88
89
|
file.close()
|
|
89
90
|
|
|
90
|
-
if
|
|
91
|
+
if not GdhPatterns.noted_work(content).is_empty():
|
|
91
92
|
todo_count += 1
|
|
92
93
|
|
|
93
94
|
# Work noted for later, which no script fails on, so said rather than failed. A count of
|
|
@@ -8,6 +8,7 @@ const GdhFileWalk = preload("file_walk.gd")
|
|
|
8
8
|
const GdhImportPipeline = preload("import_pipeline.gd")
|
|
9
9
|
const GdhInputActions = preload("input_actions.gd")
|
|
10
10
|
const GdhLog = preload("logger.gd")
|
|
11
|
+
const GdhPatterns = preload("patterns.gd")
|
|
11
12
|
const GdhRead = preload("reading.gd")
|
|
12
13
|
const GdhSceneConnections = preload("scene_connections.gd")
|
|
13
14
|
const GdhScriptCheck = preload("script_check.gd")
|
|
@@ -205,11 +206,12 @@ func validate_project(params: Dictionary) -> Dictionary:
|
|
|
205
206
|
var content: String = file.get_as_text()
|
|
206
207
|
file.close()
|
|
207
208
|
|
|
208
|
-
|
|
209
|
+
var noted: Array[String] = GdhPatterns.noted_work(content)
|
|
210
|
+
if not noted.is_empty():
|
|
209
211
|
script_issues.append(
|
|
210
212
|
{ "path": script_path, "issue": "Contains TODO/FIXME comments" }
|
|
211
213
|
)
|
|
212
|
-
if "pass
|
|
214
|
+
if "pass" in noted:
|
|
213
215
|
script_issues.append(
|
|
214
216
|
{ "path": script_path, "issue": "Contains unimplemented functions" }
|
|
215
217
|
)
|
package/build/keeper.js
CHANGED
|
@@ -8895,6 +8895,7 @@ function frameEndLine() {
|
|
|
8895
8895
|
return frameEnd;
|
|
8896
8896
|
}
|
|
8897
8897
|
var READ_MS = 5000;
|
|
8898
|
+
var EVALUATE_MS = 60000;
|
|
8898
8899
|
var REPLIES = new Set([
|
|
8899
8900
|
"debug_enter",
|
|
8900
8901
|
"debug_exit",
|
|
@@ -9478,10 +9479,10 @@ class GameDebugger {
|
|
|
9478
9479
|
}
|
|
9479
9480
|
this.forget("evaluation_return");
|
|
9480
9481
|
this.send("evaluate", [expression, frame], held.thread);
|
|
9481
|
-
const answered = await this.next(GameDebugger.named("evaluation_return"),
|
|
9482
|
+
const answered = await this.next(GameDebugger.named("evaluation_return"), EVALUATE_MS);
|
|
9482
9483
|
if (answered === null || !("data" in answered)) {
|
|
9483
9484
|
return {
|
|
9484
|
-
refused: `The game worked out nothing for ${expression} within ${
|
|
9485
|
+
refused: `The game worked out nothing for ${expression} within ${EVALUATE_MS / 1000}s. The first expression a game evaluates loads the script of every global class in the project first, static initialisers included, so one of those may still be running. Ask again.`
|
|
9485
9486
|
};
|
|
9486
9487
|
}
|
|
9487
9488
|
const [, , type, value, hint] = answered.data;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.46",
|
|
4
4
|
"mcpName": "io.github.Aureliolo/gdharness",
|
|
5
5
|
"description": "A harness for driving a Godot 4 project from an agent: editor addons, a runtime bridge into the running game, an MCP server in front of them, and a CLI that installs and diagnoses the Godot side.",
|
|
6
6
|
"type": "module",
|