gdharness 1.0.25 → 1.0.26

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
@@ -16489,7 +16489,7 @@ var init_tool_definitions = __esm(() => {
16489
16489
  property: {
16490
16490
  type: "string",
16491
16491
  ops: ["property", "find"],
16492
- description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
16492
+ description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
16493
16493
  },
16494
16494
  depth: { type: "number", ops: ["tree"], description: "tree: levels to descend. Default 3." },
16495
16495
  includeProperties: {
@@ -48,6 +48,22 @@ const MAP_CALLS: Array[String] = [
48
48
  "get_typed_value_class_name",
49
49
  "get_typed_value_script",
50
50
  ]
51
+ ## The calls a path makes on a packed list, which has fewer than a list: measured on 4.7.2, it has no
52
+ ## front(), back(), max(), min() or hash().
53
+ const PACKED_CALLS: Array[String] = ["size", "is_empty", "duplicate", "to_byte_array"]
54
+ ## The packed lists, which a game keeps names and points in as often as it keeps them in an Array.
55
+ const PACKED: Array[int] = [
56
+ TYPE_PACKED_BYTE_ARRAY,
57
+ TYPE_PACKED_INT32_ARRAY,
58
+ TYPE_PACKED_INT64_ARRAY,
59
+ TYPE_PACKED_FLOAT32_ARRAY,
60
+ TYPE_PACKED_FLOAT64_ARRAY,
61
+ TYPE_PACKED_STRING_ARRAY,
62
+ TYPE_PACKED_VECTOR2_ARRAY,
63
+ TYPE_PACKED_VECTOR3_ARRAY,
64
+ TYPE_PACKED_COLOR_ARRAY,
65
+ TYPE_PACKED_VECTOR4_ARRAY,
66
+ ]
51
67
  ## The calls an empty list has nothing to answer, which the engine reports as an error when asked.
52
68
  const NEEDS_AN_ELEMENT: Array[String] = ["front", "back", "pick_random"]
53
69
 
@@ -142,7 +158,13 @@ static func object_at(root: Node, node: Node, node_path: String, given: Variant)
142
158
  ## Whether a step can be taken into [param value] at all: an object, a list or a map can be walked
143
159
  ## into, and a number or a string is where a path ends whether the caller meant it to or not.
144
160
  static func _can_hold(value: Variant) -> bool:
145
- return value is Object or value is Array or value is Dictionary
161
+ return value is Object or value is Array or value is Dictionary or packed(value)
162
+
163
+
164
+ ## Whether [param value] is a packed list. Stepped into the way a list is, by index, and a write into
165
+ ## one reaches the game's own copy, since packed lists share their storage until one is duplicated.
166
+ static func packed(value: Variant) -> bool:
167
+ return typeof(value) in PACKED
146
168
 
147
169
 
148
170
  ## What is wrong with reading [param named] off [param holder], or "" when nothing is.
@@ -168,6 +190,10 @@ static func can_read(holder: Variant, named: String) -> bool:
168
190
  if not list_call.is_empty():
169
191
  return list_call in LIST_CALLS and not (items.is_empty() and list_call in NEEDS_AN_ELEMENT)
170
192
  return _index_in(named, items.size()) != -1
193
+ if packed(holder):
194
+ if not method_of(named).is_empty():
195
+ return method_of(named) in PACKED_CALLS
196
+ return _index_in(named, len(holder)) != -1
171
197
  if holder is Dictionary:
172
198
  var map: Dictionary = holder
173
199
  if not method_of(named).is_empty():
@@ -209,6 +235,10 @@ static func write(holder: Variant, named: String, value: Variant) -> void:
209
235
  var items: Array = holder
210
236
  items[_index_in(named, items.size())] = value
211
237
  return
238
+ if packed(holder):
239
+ var list: Variant = holder
240
+ list[_index_in(named, len(holder))] = value
241
+ return
212
242
  if holder is Dictionary:
213
243
  var map: Dictionary = holder
214
244
  # Written back under the key it was found under, since a map keyed by StringName and one
@@ -225,11 +255,14 @@ static func write(holder: Variant, named: String, value: Variant) -> void:
225
255
  ## What [param holder] holds under [param named], or answers to it when [param named] is a call.
226
256
  ## Only ever called once [method can_read] agrees.
227
257
  static func read_under(holder: Variant, named: String) -> Variant:
228
- if (holder is Array or holder is Dictionary) and not method_of(named).is_empty():
258
+ if (holder is Array or holder is Dictionary or packed(holder)) and not method_of(named).is_empty():
229
259
  return Callable.create(holder, method_of(named)).call()
230
260
  if holder is Array:
231
261
  var items: Array = holder
232
262
  return items[_index_in(named, items.size())]
263
+ if packed(holder):
264
+ var list: Variant = holder
265
+ return list[_index_in(named, len(holder))]
233
266
  if holder is Dictionary:
234
267
  var map: Dictionary = holder
235
268
  if map.has(named):
@@ -269,6 +302,10 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
269
302
  if not container_call.is_empty():
270
303
  return container_calls_refused(called_as, "a list", container_call)
271
304
  return "%s is a list of %d, so there is no %s in it" % [called_as, items.size(), named]
305
+ if packed(holder):
306
+ if not container_call.is_empty():
307
+ return container_calls_refused(called_as, "a packed list", container_call)
308
+ return "%s is a packed list of %d, so there is no %s in it" % [called_as, len(holder), named]
272
309
  if holder is Dictionary:
273
310
  if not container_call.is_empty():
274
311
  return container_calls_refused(called_as, "a map", container_call)
@@ -303,10 +340,14 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
303
340
  return "%s has no property %s" % [called_as, named]
304
341
 
305
342
 
306
- ## Why [param method] is not a call a path makes on [param sort] ("a list" or "a map"), naming
307
- ## the calls it does make.
343
+ ## Why [param method] is not a call a path makes on [param sort] ("a list", "a packed list" or "a
344
+ ## map"), naming the calls it does make.
308
345
  static func container_calls_refused(called_as: String, sort: String, method: String) -> String:
309
- var calls: Array[String] = LIST_CALLS if sort == "a list" else MAP_CALLS
346
+ var calls: Array[String] = MAP_CALLS
347
+ if sort == "a list":
348
+ calls = LIST_CALLS
349
+ elif sort == "a packed list":
350
+ calls = PACKED_CALLS
310
351
  var spelled: Array[String] = []
311
352
  for each: String in calls:
312
353
  spelled.append(each + "()")
@@ -705,7 +705,11 @@ func call_method(params: Dictionary) -> Dictionary:
705
705
  # rather than as "has no method", which reads as a misspelling of a method that was never going
706
706
  # to be there, and with the element spelled out, since a method on what it holds is the usual aim.
707
707
  if not reached["holder"] is Object:
708
- var sort: String = "a list" if reached["holder"] is Array else "a map"
708
+ var sort: String = "a list"
709
+ if reached["holder"] is Dictionary:
710
+ sort = "a map"
711
+ elif Paths.packed(reached["holder"]):
712
+ sort = "a packed list"
709
713
  var spelled: String = str(reached["name"])
710
714
  if Paths.method_of(spelled).is_empty():
711
715
  spelled += "()"
package/build/index.js CHANGED
@@ -30496,7 +30496,7 @@ var TOOL_SPECS = [
30496
30496
  property: {
30497
30497
  type: "string",
30498
30498
  ops: ["property", "find"],
30499
- description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
30499
+ description: 'property: which one to read. find: read this one off every node matched, so a panel of labels is one call rather than one per label. Colons read through what a node holds, "_game:clock:speed", which is where a game keeps what is worth asking about. A number or a key steps into a list or a map, "_game:run:roster:0:traits", which is how the lists a game keeps its state in are walked: a roster, a board, an in-tray. A packed list, such as a PackedStringArray, steps the same way. A negative number counts from the end. A step written as a call, "get_viewport():gui_get_focus_owner()", calls a method that takes no arguments and walks into what it returned, which is how a question only a method answers is read in one call: which control holds the focus in the viewport this one is in. A list or a map answers the calls that read it and take no arguments, "_game:run:board:size()" or "_game:inbox:keys()", and not those that change it, such as clear() or sort(). A step that is not there is named, and says what was there instead.'
30500
30500
  },
30501
30501
  depth: { type: "number", ops: ["tree"], description: "tree: levels to descend. Default 3." },
30502
30502
  includeProperties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
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",