gdharness 1.2.4 → 1.2.5
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
CHANGED
|
@@ -18898,6 +18898,12 @@ var init_tool_definitions = __esm(() => {
|
|
|
18898
18898
|
ops: ["until"],
|
|
18899
18899
|
description: "until: the value to wait for, fitted to the property's type and compared at the precision the property keeps, so -1.1 meets a 32-bit float holding -1.10000002. A value the property would have to cut is refused: a fraction on a whole number, a number other than 0 or 1 on a bool. On a property holding an object, whether declared to or untyped and holding one or null, the text null means null. One declared to hold an object refuses any value other than null, since it can never hold it."
|
|
18900
18900
|
},
|
|
18901
|
+
compare: {
|
|
18902
|
+
type: "string",
|
|
18903
|
+
enum: ["==", "!=", ">=", "<=", ">", "<"],
|
|
18904
|
+
ops: ["until"],
|
|
18905
|
+
description: "until, with a property: how what the property reads stands to value before the wait is met. Default ==. != waits for it to read as anything else. >=, <=, > and < put a number in order, met on the first look past the value or level with it at the precision the property keeps, which is how a counter that steps rather than lands is waited on: a day counter on a fast clock went from 31 to 34 between two looks, and a wait for 32 ran out. For these the value is the number it is rather than fitted to the property, so a fraction on a whole-number property is a threshold rather than refused, and a property holding anything but a number is refused."
|
|
18906
|
+
},
|
|
18901
18907
|
says: {
|
|
18902
18908
|
type: "string",
|
|
18903
18909
|
ops: ["until"],
|
|
@@ -48698,6 +48704,7 @@ ${staleNote}`}`
|
|
|
48698
48704
|
path: nodePath,
|
|
48699
48705
|
property: readString(args, "property") ?? "",
|
|
48700
48706
|
value: args["value"],
|
|
48707
|
+
...readString(args, "compare") === undefined ? {} : { compare: readString(args, "compare") },
|
|
48701
48708
|
...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
|
|
48702
48709
|
...readBoolean(args, "includeHidden") === undefined ? {} : { include_hidden: readBoolean(args, "includeHidden") },
|
|
48703
48710
|
timeout_ms: timeoutMs
|
|
@@ -21,6 +21,11 @@ const MOST_FRAMES: int = 600
|
|
|
21
21
|
## gives looking about a tenth of the time.
|
|
22
22
|
const LOOKS_APART: int = 9
|
|
23
23
|
|
|
24
|
+
## How a property wait compares what it reads with the value: equality by default, and order for a
|
|
25
|
+
## number that steps rather than lands, as a day counter on a fast clock goes from 31 to 34 between
|
|
26
|
+
## two looks and a wait for 32 never sees it.
|
|
27
|
+
const COMPARES: Array[String] = ["==", "!=", ">=", "<=", ">", "<"]
|
|
28
|
+
|
|
24
29
|
var _host: Node
|
|
25
30
|
var _values: Values
|
|
26
31
|
|
|
@@ -107,8 +112,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
107
112
|
# path, a property compares one value on one node. Taking says and dropping the other left a
|
|
108
113
|
# caller watching a screen while believing they were watching a property, and the answer says
|
|
109
114
|
# which of them it is about only if you already know that says wins.
|
|
110
|
-
if not says.is_empty() and (not property.is_empty() or params.has("value")):
|
|
111
|
-
var other: String =
|
|
115
|
+
if not says.is_empty() and (not property.is_empty() or params.has("value") or params.has("compare")):
|
|
116
|
+
var other: String = (
|
|
117
|
+
property if not property.is_empty() else ("value" if params.has("value") else "compare")
|
|
118
|
+
)
|
|
112
119
|
return {
|
|
113
120
|
"type": "error",
|
|
114
121
|
"message":
|
|
@@ -123,10 +130,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
123
130
|
if not says.is_empty():
|
|
124
131
|
var include_hidden: bool = Read.as_bool(params.get("include_hidden", false))
|
|
125
132
|
return await _wait_until_said(node_path, says, timeout_ms, include_hidden)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
var unasked: String = _property_wait_unasked(property, params)
|
|
134
|
+
if not unasked.is_empty():
|
|
135
|
+
return {"type": "error", "message": unasked}
|
|
136
|
+
var compare: String = str(params.get("compare", "=="))
|
|
130
137
|
|
|
131
138
|
var node: Node = standing["node"]
|
|
132
139
|
|
|
@@ -139,6 +146,8 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
139
146
|
return watched
|
|
140
147
|
|
|
141
148
|
var current: Variant = watched["value"]
|
|
149
|
+
if compare in [">=", "<=", ">", "<"]:
|
|
150
|
+
return await _wait_in_order(node, params, current, timeout_ms)
|
|
142
151
|
var wanted: Variant = _values.fitted(params["value"], typeof(current))
|
|
143
152
|
# Read against what the property is declared as, not only against what it holds this frame: an
|
|
144
153
|
# object property holding null holds nothing typed, so any value was taken as it came and compared
|
|
@@ -173,9 +182,10 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
173
182
|
# At the precision the property keeps, as set compares: a float arrives as 64 bits and most
|
|
174
183
|
# engine properties keep 32, so h_offset waited on as -1.1 read -1.10000002384186 every frame and
|
|
175
184
|
# the wait ran out on a value the game had reached.
|
|
185
|
+
var differs: bool = compare == "!="
|
|
176
186
|
while (
|
|
177
187
|
Values.comparable(current, wanted)
|
|
178
|
-
and
|
|
188
|
+
and Changes.held_already(wanted, current) == differs
|
|
179
189
|
and Time.get_ticks_msec() - started < timeout_ms
|
|
180
190
|
):
|
|
181
191
|
await _host.get_tree().process_frame
|
|
@@ -191,13 +201,111 @@ func wait_until(params: Dictionary) -> Dictionary:
|
|
|
191
201
|
"type": "condition",
|
|
192
202
|
"path": node_path,
|
|
193
203
|
"property": property,
|
|
194
|
-
"
|
|
204
|
+
"compare": compare,
|
|
205
|
+
"met": Values.comparable(current, wanted) and Changes.held_already(wanted, current) != differs,
|
|
195
206
|
"value": _values.serialize(current),
|
|
196
207
|
"elapsed_ms": Time.get_ticks_msec() - started,
|
|
197
208
|
"frames": frames,
|
|
198
209
|
}
|
|
199
210
|
|
|
200
211
|
|
|
212
|
+
## What a property wait is missing or was given wrongly, or "" when it can go ahead.
|
|
213
|
+
static func _property_wait_unasked(property: String, params: Dictionary) -> String:
|
|
214
|
+
if property.is_empty():
|
|
215
|
+
return "A property and a value, or says, are required"
|
|
216
|
+
if not params.has("value"):
|
|
217
|
+
return "A value to wait for is required"
|
|
218
|
+
var compare: String = str(params.get("compare", "=="))
|
|
219
|
+
if not COMPARES.has(compare):
|
|
220
|
+
return 'compare is one of %s, not "%s"' % [", ".join(COMPARES), compare]
|
|
221
|
+
return ""
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
## [method wait_until] for a number compared by order: met on the first look at which the property
|
|
225
|
+
## is past the value, or level with it at the precision the property keeps, as `==` compares.
|
|
226
|
+
##
|
|
227
|
+
## The value is read as the number it is rather than fitted to the property's type, since fitting
|
|
228
|
+
## truncates: 31.5 on a whole-number property became 31, and a wait for "at least 31.5" was met at 31.
|
|
229
|
+
func _wait_in_order(node: Node, params: Dictionary, current: Variant, timeout_ms: int) -> Dictionary:
|
|
230
|
+
var node_path: String = str(params.get("path", ""))
|
|
231
|
+
var property: String = str(params.get("property", ""))
|
|
232
|
+
var compare: String = str(params.get("compare", ""))
|
|
233
|
+
var given: Variant = params["value"]
|
|
234
|
+
# The property first: a string waited on in order is refused for being a string, whatever value
|
|
235
|
+
# came with it, since no number would make the wait answerable.
|
|
236
|
+
var unordered: String = _no_order(current, node_path, property, compare)
|
|
237
|
+
if not unordered.is_empty():
|
|
238
|
+
return {"type": "error", "message": unordered}
|
|
239
|
+
var number: Variant = given
|
|
240
|
+
if given is String:
|
|
241
|
+
var text: String = given
|
|
242
|
+
number = Read.json_or_null(text)
|
|
243
|
+
if typeof(number) != TYPE_INT and typeof(number) != TYPE_FLOAT:
|
|
244
|
+
return {
|
|
245
|
+
"type": "error",
|
|
246
|
+
"message":
|
|
247
|
+
(
|
|
248
|
+
"compare %s puts a number in order, and the value to wait for is %s: give a number."
|
|
249
|
+
% [compare, str(given)]
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
var limit: float = Read.as_float(number)
|
|
253
|
+
|
|
254
|
+
var started: int = Time.get_ticks_msec()
|
|
255
|
+
var frames: int = 0
|
|
256
|
+
while not _in_order(compare, limit, current) and Time.get_ticks_msec() - started < timeout_ms:
|
|
257
|
+
await _host.get_tree().process_frame
|
|
258
|
+
frames += 1
|
|
259
|
+
if not is_instance_valid(node):
|
|
260
|
+
return {"type": "error", "message": "%s was freed while waiting" % node_path}
|
|
261
|
+
var watched: Dictionary = _watched(node, node_path, property)
|
|
262
|
+
if watched.has("message"):
|
|
263
|
+
return watched
|
|
264
|
+
current = watched["value"]
|
|
265
|
+
unordered = _no_order(current, node_path, property, compare)
|
|
266
|
+
if not unordered.is_empty():
|
|
267
|
+
return {"type": "error", "message": unordered}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
"type": "condition",
|
|
271
|
+
"path": node_path,
|
|
272
|
+
"property": property,
|
|
273
|
+
"compare": compare,
|
|
274
|
+
"met": _in_order(compare, limit, current),
|
|
275
|
+
"value": _values.serialize(current),
|
|
276
|
+
"elapsed_ms": Time.get_ticks_msec() - started,
|
|
277
|
+
"frames": frames,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
## Why [param current] cannot be put in order, or "" when it is a number.
|
|
282
|
+
static func _no_order(current: Variant, node_path: String, property: String, compare: String) -> String:
|
|
283
|
+
if typeof(current) == TYPE_INT or typeof(current) == TYPE_FLOAT:
|
|
284
|
+
return ""
|
|
285
|
+
return (
|
|
286
|
+
"%s.%s holds %s, which has no order, so compare %s cannot be asked of it: use == or !=."
|
|
287
|
+
% [node_path, property, type_string(typeof(current)), compare]
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
## Whether [param current], a number, stands to [param limit] as [param compare] says. Level counts
|
|
292
|
+
## at the precision the property keeps, so a 32-bit float holding 0.30000001 is at least 0.3.
|
|
293
|
+
static func _in_order(compare: String, limit: float, current: Variant) -> bool:
|
|
294
|
+
var reading: float = Read.as_float(current)
|
|
295
|
+
var level: bool = (
|
|
296
|
+
reading == limit or (typeof(current) == TYPE_FLOAT and Changes.held_already(limit, current))
|
|
297
|
+
)
|
|
298
|
+
match compare:
|
|
299
|
+
">=":
|
|
300
|
+
return level or reading > limit
|
|
301
|
+
"<=":
|
|
302
|
+
return level or reading < limit
|
|
303
|
+
">":
|
|
304
|
+
return not level and reading > limit
|
|
305
|
+
_:
|
|
306
|
+
return not level and reading < limit
|
|
307
|
+
|
|
308
|
+
|
|
201
309
|
## Why [param given] would have to be cut to be waited on a whole number or a truth, or "" when not.
|
|
202
310
|
##
|
|
203
311
|
## A number arrives from JSON as a float, so one is converted to what the property holds, and the
|
package/build/index.js
CHANGED
|
@@ -35025,6 +35025,12 @@ var TOOL_SPECS = [
|
|
|
35025
35025
|
ops: ["until"],
|
|
35026
35026
|
description: "until: the value to wait for, fitted to the property's type and compared at the precision the property keeps, so -1.1 meets a 32-bit float holding -1.10000002. A value the property would have to cut is refused: a fraction on a whole number, a number other than 0 or 1 on a bool. On a property holding an object, whether declared to or untyped and holding one or null, the text null means null. One declared to hold an object refuses any value other than null, since it can never hold it."
|
|
35027
35027
|
},
|
|
35028
|
+
compare: {
|
|
35029
|
+
type: "string",
|
|
35030
|
+
enum: ["==", "!=", ">=", "<=", ">", "<"],
|
|
35031
|
+
ops: ["until"],
|
|
35032
|
+
description: "until, with a property: how what the property reads stands to value before the wait is met. Default ==. != waits for it to read as anything else. >=, <=, > and < put a number in order, met on the first look past the value or level with it at the precision the property keeps, which is how a counter that steps rather than lands is waited on: a day counter on a fast clock went from 31 to 34 between two looks, and a wait for 32 ran out. For these the value is the number it is rather than fitted to the property, so a fraction on a whole-number property is a threshold rather than refused, and a property holding anything but a number is refused."
|
|
35033
|
+
},
|
|
35028
35034
|
says: {
|
|
35029
35035
|
type: "string",
|
|
35030
35036
|
ops: ["until"],
|
|
@@ -41195,6 +41201,7 @@ ${staleNote}`}`
|
|
|
41195
41201
|
path: nodePath,
|
|
41196
41202
|
property: readString(args, "property") ?? "",
|
|
41197
41203
|
value: args["value"],
|
|
41204
|
+
...readString(args, "compare") === undefined ? {} : { compare: readString(args, "compare") },
|
|
41198
41205
|
...readNonEmptyString(args, "says") === undefined ? {} : { says: readNonEmptyString(args, "says") },
|
|
41199
41206
|
...readBoolean(args, "includeHidden") === undefined ? {} : { include_hidden: readBoolean(args, "includeHidden") },
|
|
41200
41207
|
timeout_ms: timeoutMs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gdharness",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.5",
|
|
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",
|