gdharness 1.3.70 → 1.3.71

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
@@ -20498,7 +20498,7 @@ var init_tool_definitions = __esm(() => {
20498
20498
  };
20499
20499
  PROPERTIES = {
20500
20500
  type: "object",
20501
- description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. One carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y". A component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". A property holding a Resource takes the res:// path of a file, which a read answers with its class and, when its script declares one, scriptClass, or a resource built into the scene as {"_type": "CircleShape2D", "radius": 8}, its class (an engine class or a script's class_name) beside its properties. A typed list or map of resources or nodes, such as Array[Item], takes each in the same forms, a node by its path. A read answers a resource built into the scene in that form, with what it holds away from the defaults, so it too can be written to another node. script takes the res:// path of a script to attach, one with no class_name included, and is attached before the rest are set, so the properties it declares can be given beside it. Each value is read back once set, and one the engine changed or refused is refused with what it holds. An enum property takes its number or the name of a value: as the script spells it, as the inspector shows it, or for an engine enum as its constant, so Add or BLEND_MODE_ADD, and Bottom or VERTICAL_ALIGNMENT_BOTTOM for a global one. A value past an export range, or a number no value of an enum has, is written as given and named under outsideDeclared. A key may reach into a resource built into this one: the property holding it, a slash, then one of its own, so a TileSet's first atlas source takes "sources/0/0:0/0/probability". A list of such resources is stepped into by an element's index, so "lines/1/text" is the text of the second resource in lines and the others are left as they were. A resource kept in a file of its own, or built into the scene an instance comes from, is written in that file, or replaced here by one of this file's own. The next element of a list is made by writing to it, as the inspector's Add Element makes it: physics_layer_0/collision_layer on a TileSet with no physics layer makes its first, and "sources/0/0:0/0/physics_layer_0/polygon_0/points" a tile's first collision polygon on that layer. ${WHOLE_NUMBERS}`,
20501
+ description: `Properties to set, keyed by Godot property name. Vectors, colours and the like may be written as {"x": 1, "y": 2} or tagged {"_type": "Vector2", "x": 1, "y": 2}, which is the form a read answers with, so a value read off one node can be written straight to another. One carrying a field its type has not, such as a z for a Vector2, is refused naming it. A colour may also be given as its html code, "#ff8000" or "#ff800080". A key may also be a colon path into one of the components of a vector, a colour, a rectangle or a transform, as GDScript names them, so one axis is written alone: "position:x", "modulate:a", "region_rect:size:y". A component the value does not have is refused, naming the ones it has. A property holding a node takes the path to it from the node being set, such as "../Player". A property holding a Resource takes the res:// path of a file, which a read answers with its class and, when its script declares one, scriptClass, or a resource built into the scene as {"_type": "CircleShape2D", "radius": 8}, its class (an engine class or a script's class_name) beside its properties. A typed list or map of resources or nodes, such as Array[Item], takes each in the same forms, a node by its path. A read answers a resource built into the scene in that form, with what it holds away from the defaults, so it too can be written to another node. script takes the res:// path of a script to attach, one with no class_name included, and is attached before the rest are set, so the properties it declares can be given beside it. Each value is read back once set, and one the engine changed or refused is refused with what it holds. An enum property takes its number or the name of a value: as the script spells it, as the inspector shows it, or for an engine enum as its constant, so Add or BLEND_MODE_ADD, and Bottom or VERTICAL_ALIGNMENT_BOTTOM for a global one. A value past an export range, or a number no value of an enum has, is written as given and named under outsideDeclared. A key may reach into a resource built into this one: the property holding it, a slash, then one of its own, so a TileSet's first atlas source takes "sources/0/0:0/0/probability". A list of such resources is stepped into by an element's index, so "lines/1/text" is the text of the second resource in lines and the others are left as they were. A resource kept in a file of its own, or built into the scene an instance comes from, is written in that file, or replaced here by one of this file's own. The next element of a list is made by writing to it, as the inspector's Add Element makes it: physics_layer_0/collision_layer on a TileSet with no physics layer makes its first, and "sources/0/0:0/0/physics_layer_0/polygon_0/points" a tile's first collision polygon on that layer. null for a "metadata/" key takes that entry off, as the inspector's delete button does, and the answer names it under metadataRemoved. ${WHOLE_NUMBERS}`,
20502
20502
  additionalProperties: true
20503
20503
  };
20504
20504
  XY = {
@@ -69,6 +69,9 @@ var _outside_declared: Dictionary = {}
69
69
  # The node paths the last write_all wrote that name no node from the node written, by property, as
70
70
  # a sentence saying so.
71
71
  var _names_no_node: Dictionary = {}
72
+ ## The metadata entries the last write_all took off, by the property names given. Godot reads a null
73
+ ## set on a metadata entry as its removal, as the inspector's delete button does.
74
+ var metadata_removed: Array[String] = []
72
75
 
73
76
 
74
77
  ## Turns what arrived over the wire into the Godot value a property wants.
@@ -204,6 +207,7 @@ func write_all(target: Object, properties: Dictionary) -> String:
204
207
  _names_no_node = {}
205
208
  moved_by_engine = {}
206
209
  unheld = ""
210
+ metadata_removed = []
207
211
  var named: Dictionary = {}
208
212
  for key: Variant in properties:
209
213
  named[str(key).get_slice(":", 0)] = true
@@ -238,6 +242,15 @@ func write_all(target: Object, properties: Dictionary) -> String:
238
242
  var place: Array = GdhPropertyPaths.holder_of(target, property, home)
239
243
  var holder: Object = place[0]
240
244
  var name: String = place[1]
245
+ var entry: String = name.trim_prefix("metadata/")
246
+ if (
247
+ properties[key] == null
248
+ and name.begins_with("metadata/")
249
+ and holder.has_meta(entry)
250
+ ):
251
+ holder.remove_meta(entry)
252
+ metadata_removed.append(property)
253
+ continue
241
254
  var declared: Dictionary = declaration(holder, name)
242
255
  if declared.is_empty():
243
256
  var elsewhere: String = GdhPropertyPaths.held_elsewhere(holder, name, home)
@@ -329,7 +342,7 @@ func write_all(target: Object, properties: Dictionary) -> String:
329
342
  )
330
343
  for property: String in before:
331
344
  var now: Variant = target.get(property)
332
- if not GdhSame.same(now, before[property]):
345
+ if not metadata_removed.has(property) and not GdhSame.same(now, before[property]):
333
346
  _also_changed[property] = [before[property], now]
334
347
  if target is Node:
335
348
  _names_no_node = GdhDeclared.naming_nothing(target as Node, given)
@@ -384,6 +397,8 @@ func _laid_out(control: Control, name: String) -> String:
384
397
  ## as a scene answer names one. Called before the scene is written, since the write frees the nodes
385
398
  ## these values can hold.
386
399
  func note_write(answer: Dictionary, relative_to: Node = null) -> void:
400
+ if not metadata_removed.is_empty():
401
+ answer["metadataRemoved"] = metadata_removed.duplicate()
387
402
  if not _outside_declared.is_empty():
388
403
  answer["outsideDeclared"] = _outside_declared.duplicate()
389
404
  if not _names_no_node.is_empty():
@@ -338,7 +338,10 @@ func modify_resource(args: Dictionary) -> Dictionary:
338
338
  )
339
339
  if not also.is_empty():
340
340
  answer["alsoSaved"] = also
341
- answer["properties"] = _held(resource, properties.keys())
341
+ var kept: Array = properties.keys().filter(
342
+ func(key: Variant) -> bool: return not _properties.metadata_removed.has(str(key))
343
+ )
344
+ answer["properties"] = _held(resource, kept)
342
345
  return answer
343
346
 
344
347
 
@@ -1191,7 +1191,9 @@ func set_node_properties(args: Dictionary) -> Dictionary:
1191
1191
  + "since a placeholder loads without its children."
1192
1192
  )
1193
1193
  )
1194
- if wants_placeholder and editable == null and scene.root.is_editable_instance(node):
1194
+ # The engine asks the root whether it is an editable instance of itself only with an error.
1195
+ var children_open: bool = node != scene.root and scene.root.is_editable_instance(node)
1196
+ if wants_placeholder and editable == null and children_open:
1195
1197
  return scene.refuse(
1196
1198
  (
1197
1199
  (
@@ -1245,8 +1247,10 @@ func set_node_properties(args: Dictionary) -> Dictionary:
1245
1247
  return scene.refuse(ungrouped)
1246
1248
 
1247
1249
  var held: Dictionary = {}
1250
+ var removed: Array[String] = _properties.metadata_removed.duplicate()
1248
1251
  for key: Variant in properties:
1249
- held[str(key)] = _readable(node, _properties.read_at(node, str(key)))
1252
+ if not removed.has(str(key)):
1253
+ held[str(key)] = _readable(node, _properties.read_at(node, str(key)))
1250
1254
  var new_path: String = scene.path_of(node)
1251
1255
  var answer: Dictionary = { "ok": true, "nodePath": new_path, "properties": held }
1252
1256
  _properties.note_write(answer, node)
@@ -69,6 +69,9 @@ var _outside_declared: Dictionary = {}
69
69
  # The node paths the last write_all wrote that name no node from the node written, by property, as
70
70
  # a sentence saying so.
71
71
  var _names_no_node: Dictionary = {}
72
+ ## The metadata entries the last write_all took off, by the property names given. Godot reads a null
73
+ ## set on a metadata entry as its removal, as the inspector's delete button does.
74
+ var metadata_removed: Array[String] = []
72
75
 
73
76
 
74
77
  ## Turns what arrived over the wire into the Godot value a property wants.
@@ -204,6 +207,7 @@ func write_all(target: Object, properties: Dictionary) -> String:
204
207
  _names_no_node = {}
205
208
  moved_by_engine = {}
206
209
  unheld = ""
210
+ metadata_removed = []
207
211
  var named: Dictionary = {}
208
212
  for key: Variant in properties:
209
213
  named[str(key).get_slice(":", 0)] = true
@@ -238,6 +242,15 @@ func write_all(target: Object, properties: Dictionary) -> String:
238
242
  var place: Array = GdhPropertyPaths.holder_of(target, property, home)
239
243
  var holder: Object = place[0]
240
244
  var name: String = place[1]
245
+ var entry: String = name.trim_prefix("metadata/")
246
+ if (
247
+ properties[key] == null
248
+ and name.begins_with("metadata/")
249
+ and holder.has_meta(entry)
250
+ ):
251
+ holder.remove_meta(entry)
252
+ metadata_removed.append(property)
253
+ continue
241
254
  var declared: Dictionary = declaration(holder, name)
242
255
  if declared.is_empty():
243
256
  var elsewhere: String = GdhPropertyPaths.held_elsewhere(holder, name, home)
@@ -329,7 +342,7 @@ func write_all(target: Object, properties: Dictionary) -> String:
329
342
  )
330
343
  for property: String in before:
331
344
  var now: Variant = target.get(property)
332
- if not GdhSame.same(now, before[property]):
345
+ if not metadata_removed.has(property) and not GdhSame.same(now, before[property]):
333
346
  _also_changed[property] = [before[property], now]
334
347
  if target is Node:
335
348
  _names_no_node = GdhDeclared.naming_nothing(target as Node, given)
@@ -384,6 +397,8 @@ func _laid_out(control: Control, name: String) -> String:
384
397
  ## as a scene answer names one. Called before the scene is written, since the write frees the nodes
385
398
  ## these values can hold.
386
399
  func note_write(answer: Dictionary, relative_to: Node = null) -> void:
400
+ if not metadata_removed.is_empty():
401
+ answer["metadataRemoved"] = metadata_removed.duplicate()
387
402
  if not _outside_declared.is_empty():
388
403
  answer["outsideDeclared"] = _outside_declared.duplicate()
389
404
  if not _names_no_node.is_empty():
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gdharness",
3
- "version": "1.3.70",
3
+ "version": "1.3.71",
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",