gdharness 1.3.59 → 1.3.61

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.
Files changed (77) hide show
  1. package/README.md +20 -20
  2. package/build/cli.js +2264 -1706
  3. package/build/godot/addons/auto_reload/auto_reload.gd +3 -3
  4. package/build/godot/addons/gdharness_editor/bridge_client.gd +4 -4
  5. package/build/godot/addons/gdharness_editor/declared_values.gd +13 -13
  6. package/build/godot/addons/gdharness_editor/file_reads.gd +6 -6
  7. package/build/godot/addons/gdharness_editor/headless_check.gd +9 -6
  8. package/build/godot/addons/gdharness_editor/property_paths.gd +3 -3
  9. package/build/godot/addons/gdharness_editor/property_values.gd +18 -11
  10. package/build/godot/addons/gdharness_editor/reading.gd +22 -18
  11. package/build/godot/addons/gdharness_editor/scene_file.gd +5 -5
  12. package/build/godot/addons/gdharness_editor/serialisation.gd +4 -4
  13. package/build/godot/addons/gdharness_editor/tools/animation_states.gd +5 -5
  14. package/build/godot/addons/gdharness_editor/tools/animation_tools.gd +6 -6
  15. package/build/godot/addons/gdharness_editor/tools/animation_tracks.gd +9 -9
  16. package/build/godot/addons/gdharness_editor/tools/class_tools.gd +3 -3
  17. package/build/godot/addons/gdharness_editor/tools/filesystem_tools.gd +5 -5
  18. package/build/godot/addons/gdharness_editor/tools/navigation_bake.gd +5 -5
  19. package/build/godot/addons/gdharness_editor/tools/play_tools.gd +2 -2
  20. package/build/godot/addons/gdharness_editor/tools/profile_tools.gd +1 -1
  21. package/build/godot/addons/gdharness_editor/tools/resource_tools.gd +5 -5
  22. package/build/godot/addons/gdharness_editor/tools/scene_groups.gd +3 -3
  23. package/build/godot/addons/gdharness_editor/tools/scene_kin.gd +3 -3
  24. package/build/godot/addons/gdharness_editor/tools/scene_paths.gd +4 -4
  25. package/build/godot/addons/gdharness_editor/tools/scene_placing.gd +3 -3
  26. package/build/godot/addons/gdharness_editor/tools/scene_tools.gd +10 -10
  27. package/build/godot/addons/gdharness_editor/tools/signal_tools.gd +1 -1
  28. package/build/godot/addons/gdharness_editor/tools/tileset_tools.gd +5 -5
  29. package/build/godot/addons/gdharness_runtime/declared_values.gd +13 -13
  30. package/build/godot/addons/gdharness_runtime/reading.gd +22 -18
  31. package/build/godot/addons/gdharness_runtime/runtime_accessibility.gd +2 -2
  32. package/build/godot/addons/gdharness_runtime/runtime_autoload.gd +89 -18
  33. package/build/godot/addons/gdharness_runtime/runtime_capture.gd +6 -6
  34. package/build/godot/addons/gdharness_runtime/runtime_changes.gd +10 -10
  35. package/build/godot/addons/gdharness_runtime/runtime_frames.gd +5 -4
  36. package/build/godot/addons/gdharness_runtime/runtime_hover.gd +3 -4
  37. package/build/godot/addons/gdharness_runtime/runtime_input.gd +8 -8
  38. package/build/godot/addons/gdharness_runtime/runtime_items.gd +1 -1
  39. package/build/godot/addons/gdharness_runtime/runtime_menus.gd +3 -3
  40. package/build/godot/addons/gdharness_runtime/runtime_mouse.gd +5 -5
  41. package/build/godot/addons/gdharness_runtime/runtime_paths.gd +20 -20
  42. package/build/godot/addons/gdharness_runtime/runtime_queries.gd +13 -13
  43. package/build/godot/addons/gdharness_runtime/runtime_says.gd +5 -5
  44. package/build/godot/addons/gdharness_runtime/runtime_targets.gd +39 -39
  45. package/build/godot/addons/gdharness_runtime/runtime_typing.gd +3 -3
  46. package/build/godot/addons/gdharness_runtime/runtime_values.gd +7 -7
  47. package/build/godot/addons/gdharness_runtime/runtime_waits.gd +8 -8
  48. package/build/godot/addons/gdharness_runtime/runtime_words.gd +5 -5
  49. package/build/godot/addons/gdharness_runtime/serialisation.gd +4 -4
  50. package/build/godot/addons/gdharness_tests/bench.gd +1 -1
  51. package/build/godot/addons/gdharness_tests/share.gd +6 -6
  52. package/build/godot/addons/gdharness_tests/suites.gd +1 -1
  53. package/build/godot/operations/audio_buses.gd +4 -4
  54. package/build/godot/operations/class_cache.gd +3 -3
  55. package/build/godot/operations/classdb_queries.gd +1 -1
  56. package/build/godot/operations/declared_values.gd +13 -13
  57. package/build/godot/operations/dependencies.gd +6 -6
  58. package/build/godot/operations/export_presets.gd +7 -7
  59. package/build/godot/operations/file_reads.gd +6 -6
  60. package/build/godot/operations/gdscript_analysis.gd +6 -6
  61. package/build/godot/operations/gdscript_authoring.gd +11 -11
  62. package/build/godot/operations/godot_operations.gd +1 -1
  63. package/build/godot/operations/import_pipeline.gd +12 -12
  64. package/build/godot/operations/input_actions.gd +3 -3
  65. package/build/godot/operations/logger.gd +1 -1
  66. package/build/godot/operations/plugins.gd +2 -2
  67. package/build/godot/operations/project_config.gd +11 -11
  68. package/build/godot/operations/project_validation.gd +1 -1
  69. package/build/godot/operations/property_paths.gd +3 -3
  70. package/build/godot/operations/property_values.gd +18 -11
  71. package/build/godot/operations/reading.gd +22 -18
  72. package/build/godot/operations/resource_files.gd +1 -1
  73. package/build/godot/operations/script_check.gd +2 -2
  74. package/build/godot/operations/serialisation.gd +4 -4
  75. package/build/godot/tests/listed.gd +28 -10
  76. package/build/keeper.js +511 -161
  77. package/package.json +1 -1
@@ -180,7 +180,7 @@ func _members(
180
180
  own.last = i - 1
181
181
  return own
182
182
 
183
- # An odd count opens a string that runs on; an even one is a string that closed on the line
183
+ # An odd count opens a string that runs on. An even one is a string that closed on the line
184
184
  # it opened on. Toggling on any count lost `const HELP := """Usage"""`, and everything after
185
185
  # it up to the next triple quote, with the state inverted from there to the end of the file.
186
186
  # The line that opens one is still read: it can be the declaration the string belongs to.
@@ -359,7 +359,7 @@ func _mark_virtuals(functions: Array[Dictionary], extends_name: String, from_pat
359
359
 
360
360
  # The native class at the top of this script's chain, or empty when the chain does not reach one.
361
361
  #
362
- # A base named by a script is followed; a base that is neither a script nor a class the engine knows
362
+ # A base named by a script is followed. A base that is neither a script nor a class the engine knows
363
363
  # leaves nothing to ask, and then no function is called virtual rather than every underscore being
364
364
  # guessed at.
365
365
  func _native_ancestor(base: String, from_path: String) -> String:
@@ -388,7 +388,7 @@ func _native_ancestor(base: String, from_path: String) -> String:
388
388
  # `res://` path or as a `class_name` in the project's class list, and a script whose base is neither
389
389
  # has no ancestor to read, which the answer says by leaving `inherits_from` short rather than by
390
390
  # refusing. Each entry carries `inherited_from`, so a caller can tell a member the script declares
391
- # from one it is given; a name a script overrides appears twice, at both lines, on purpose.
391
+ # from one it is given. A name a script overrides appears twice, at both lines, on purpose.
392
392
  func _add_inherited(answer: Dictionary, seen: Array[String]) -> void:
393
393
  var inherits_from: Array[String] = []
394
394
  var base: String = str(answer.get("extends", ""))
@@ -634,7 +634,7 @@ func _parse_variable(line: String, line_num: int) -> Dictionary:
634
634
  "is_static": GdhPatterns.without_annotations(line).begins_with("static "),
635
635
  "line": line_num,
636
636
  }
637
- # Only := types a variable by its value; one written with = alone holds any type.
637
+ # Only := types a variable by its value. One written with = alone holds any type.
638
638
  var walrus: bool = (
639
639
  "=" in var_text and var_text.split("=", true, 1)[0].strip_edges().ends_with(":")
640
640
  )
@@ -677,7 +677,7 @@ func _parse_function(line: String, line_num: int, annotated: String = "") -> Dic
677
677
  "name": name,
678
678
  "params": params,
679
679
  "return_type": return_type,
680
- # Filled in after the whole file is read, once the base is known; see `_mark_virtuals`.
680
+ # Filled in after the whole file is read, once the base is known. See `_mark_virtuals`.
681
681
  "is_virtual": false,
682
682
  "is_static": is_static,
683
683
  # Said rather than left to be inferred: an abstract method has no body, so a caller that
@@ -857,7 +857,7 @@ static func _based_on_paths(extends_name: String, inner_classes: Array) -> Array
857
857
  return paths
858
858
 
859
859
 
860
- ## The text of [param literal], a string literal as written; empty for anything else.
860
+ ## The text of [param literal], a string literal as written. Empty for anything else.
861
861
  static func _unquoted(literal: String) -> String:
862
862
  var text: String = literal.strip_edges()
863
863
  if text.length() >= 2 and text[0] in ["\"", "'"] and text[-1] == text[0]:
@@ -138,7 +138,7 @@ func create_gdscript(params: Dictionary) -> Dictionary:
138
138
 
139
139
 
140
140
  ## The uid in the .uid file beside [param path], written first when there is none, as the editor
141
- ## writes it for a script it makes; empty when it could not be written. A sidecar already there is
141
+ ## writes it for a script it makes. Empty when it could not be written. A sidecar already there is
142
142
  ## the one the engine reads the script's uid from, so it is kept.
143
143
  static func _uid_beside(path: String) -> String:
144
144
  var sidecar: String = path + ".uid"
@@ -232,7 +232,7 @@ func modify_gdscript(params: Dictionary) -> Dictionary:
232
232
  var refusal: String = "Nothing was changed: " + "; ".join(problems)
233
233
  if _redeclares(modifications, declared):
234
234
  refusal += (
235
- ". modify only adds members; to change one the script declares already,"
235
+ ". modify only adds members. To change one the script declares already,"
236
236
  + " edit its lines in the file"
237
237
  )
238
238
  return _log.failure(refusal)
@@ -290,7 +290,7 @@ func modify_gdscript(params: Dictionary) -> Dictionary:
290
290
  OS.remove_logger(caught)
291
291
  var parses: bool = _parses(made)
292
292
  # The names checked above are the script's own, and a member the base class declares, or a body
293
- # that does not compile, is found only by the engine; a script left broken by an addition breaks
293
+ # that does not compile, is found only by the engine. A script left broken by an addition breaks
294
294
  # every scene that uses it, so it goes back as it was. One already broken is written, as the
295
295
  # addition can be what mends it.
296
296
  if parsed_before and not parses:
@@ -335,7 +335,7 @@ static func _reasons(raised: Array[Dictionary], path: String) -> String:
335
335
 
336
336
  # Why a script with [param named] as its class_name and extending [param base] cannot be written,
337
337
  # or "". A class_name another script holds does not compile ("hides a global script class"), and nor
338
- # does a base no class answers to; both were written and answered as made. Classes are read off the
338
+ # does a base no class answers to. Both were written and answered as made. Classes are read off the
339
339
  # scripts as well as the class list, which a fresh clone has not written yet.
340
340
  static func _header_refusal(named: String, base: String) -> String:
341
341
  var declared: Dictionary = _declared_classes()
@@ -366,7 +366,7 @@ static func _header_refusal(named: String, base: String) -> String:
366
366
  close = GdhRead.nearest(base, ClassDB.get_class_list())
367
367
  if close.is_empty():
368
368
  return said + "."
369
- return said + "; the nearest %s %s." % ["is" if close.size() == 1 else "are", ", ".join(close)]
369
+ return said + ". The nearest %s %s." % ["is" if close.size() == 1 else "are", ", ".join(close)]
370
370
 
371
371
 
372
372
  ## Why [param template] cannot be written on [param base], or "". Each template's code calls
@@ -427,7 +427,7 @@ static func _declared_classes() -> Dictionary:
427
427
  # Every reason [param modifications] cannot be made as asked, each naming the entry by position.
428
428
  #
429
429
  # A name the script already declares is one of them, as is one the call adds twice. GDScript gives a
430
- # class one namespace for its members, so a second `maximum` does not parse; and the engine reports
430
+ # class one namespace for its members, so a second `maximum` does not parse, and the engine reports
431
431
  # only the first error it meets, so a second `func damage` behind it was written with nothing said.
432
432
  static func _modification_problems(modifications: Array, declared: Dictionary) -> Array[String]:
433
433
  var problems: Array[String] = []
@@ -541,7 +541,7 @@ static func _insert(
541
541
  # Whether the engine accepts [param script], parsed under this project's own warning settings: a
542
542
  # script that does not load is one the caller wants to hear about now, and the reason is on stderr,
543
543
  # which comes back with the answer. A script that fails to parse still loads, as one that cannot be
544
- # instantiated; an abstract one that parses can be, on 4.7.
544
+ # instantiated. An abstract one that parses can be, on 4.7.
545
545
  static func _parses(script: Script) -> bool:
546
546
  return script != null and script.can_instantiate()
547
547
 
@@ -583,7 +583,7 @@ func _add_constant(lines: Array[String], mod: Dictionary, placed: Array[Dictiona
583
583
  break
584
584
  after = _past_class_doc(lines, after)
585
585
  # Set apart from the header or the signals above and from what follows, as the style guide lays
586
- # out its groups; one below the constants already there joins them.
586
+ # out its groups. One below the constants already there joins them.
587
587
  var block: Array[String] = [const_line]
588
588
  var above: String = lines[after - 1].strip_edges() if after > 0 else ""
589
589
  var below: String = lines[after].strip_edges() if after < lines.size() else ""
@@ -620,7 +620,7 @@ func _add_variable(lines: Array[String], mod: Dictionary, placed: Array[Dictiona
620
620
 
621
621
  # Every declaration this writes carries a type, so it parses in a project that treats an
622
622
  # untyped or an inferred declaration as an error, which is the strictest setting Godot has.
623
- # A value with no type gets the type the value evaluates to; a value nothing can evaluate
623
+ # A value with no type gets the type the value evaluates to. A value nothing can evaluate
624
624
  # here, and a declaration with neither, are spelled out as Variant.
625
625
  if not var_type.is_empty():
626
626
  var_line += ": " + var_type
@@ -767,7 +767,7 @@ func _variable_insertion_point(lines: Array[String]) -> int:
767
767
  # Annotations off first. `@abstract class_name X` is one line in Godot 4.5 and later, and
768
768
  # unstripped it matches nothing here, so the header is only found when some other line
769
769
  # carries it. A script whose whole header is that one line leaves `after_header` at 0 and
770
- # takes the new line above the `class_name`, where it does not parse; one that declares
770
+ # takes the new line above the `class_name`, where it does not parse. One that declares
771
771
  # `extends` first takes it between the two, which does not parse either. Stripping also
772
772
  # turns `@export var x` into `var x`, which is why the annotation names are gone below.
773
773
  var line: String = GdhPatterns.without_annotations(lines[i])
@@ -886,7 +886,7 @@ signal state_changed(old_state: String, new_state: String)
886
886
 
887
887
  var current_state: String = ""
888
888
  ## State objects by name. A state may define enter(), exit(), process(delta) and
889
- ## physics_process(delta); whichever it has are called.
889
+ ## physics_process(delta). Whichever it has are called.
890
890
  var states: Dictionary = {}
891
891
 
892
892
 
@@ -4,7 +4,7 @@ extends SceneTree
4
4
  # The command line the server runs is
5
5
  # `godot --headless --path <project> --script <this> <operation> @file:<params.json> <answer.json>`,
6
6
  # and the one JSON object written to the answer file is the answer. Everything an operation does
7
- # lives in a sibling module; this file is the command table and the wire format.
7
+ # lives in a sibling module. This file is the command table and the wire format.
8
8
  #
9
9
  # A file rather than a line on stdout, because the project's autoloads run after `_init` and print
10
10
  # into the same stream: an autoload printing a dictionary in `_process` was the last JSON line out
@@ -4,8 +4,8 @@ const GdhRead = preload("reading.gd")
4
4
  const GdhSerialisation = preload("serialisation.gd")
5
5
  const GdhFileWalk = preload("file_walk.gd")
6
6
  const GdhLog = preload("logger.gd")
7
- ## What the engine imports with no tool outside it, for finding what has not been imported yet;
8
- ## everything imported already is found by its sidecar whatever its extension.
7
+ ## What the engine imports with no tool outside it, for finding what has not been imported yet.
8
+ ## Everything imported already is found by its sidecar whatever its extension.
9
9
  const IMPORTABLE_EXTENSIONS: Array[String] = [
10
10
  "png",
11
11
  "jpg",
@@ -95,7 +95,7 @@ func get_import_status(params: Dictionary) -> Dictionary:
95
95
  if not full_path.begins_with("res://"):
96
96
  full_path = "res://" + full_path
97
97
 
98
- # missing_source is a sidecar whose source has gone; with neither, the path names nothing
98
+ # missing_source is a sidecar whose source has gone. With neither, the path names nothing
99
99
  # the engine ever imported, and answering it as a lost source sent a caller to restore a
100
100
  # file.
101
101
  if (
@@ -300,7 +300,7 @@ static func _as_option(given: Variant, held: Variant) -> Variant:
300
300
  ## written as {"1818847073": 0.0}. The importer hands that to FontFile.opentype_feature_overrides,
301
301
  ## which keys features by their tag as an int, so the call reported success and the font kept its
302
302
  ## ligatures. A key is an int where the importer keys the option by OpenType tags, or where every
303
- ## key the option holds already is one; a value is an int where every value held is, or for a tag.
303
+ ## key the option holds already is one. A value is an int where every value held is, or for a tag.
304
304
  ## Answers the dictionary under value, or why it cannot be written under refused.
305
305
  static func _keyed_as_option(
306
306
  name: String, given: Dictionary, held: Variant, importer: String
@@ -421,7 +421,7 @@ func _import_status_of(resource_path: String, import_file_path: String) -> Dicti
421
421
  # The checks are the ones EditorFileSystem::_test_for_reimport makes, in its order, so a
422
422
  # resource reads as needing an import when the editor's next scan would import it. The
423
423
  # importer's own two cannot be asked here, where no importer is loaded, so they are made from
424
- # what was measured of the built-in ones; an importer a plugin adds is judged by its files
424
+ # what was measured of the built-in ones. An importer a plugin adds is judged by its files
425
425
  # alone.
426
426
  var sidecar: ConfigFile = ConfigFile.new()
427
427
  var stale: Dictionary = (
@@ -497,7 +497,7 @@ func _never_imported(resource_path: String) -> String:
497
497
  + extension
498
498
  + " files through Blender, which it finds through the editor setting "
499
499
  + BLENDER_SETTING
500
- + ". An editor with it set imports them on its scan; an import pass with it unset "
500
+ + ". An editor with it set imports them on its scan. An import pass with it unset "
501
501
  + "stops at the first one and imports nothing, and setting "
502
502
  + BLENDER_ENABLED
503
503
  + " to false has the engine leave them unimported"
@@ -508,7 +508,7 @@ func _never_imported(resource_path: String) -> String:
508
508
  return (
509
509
  "no importer built into the engine takes ."
510
510
  + extension
511
- + " files; one added by a plugin or an extension is known only to the editor, whose scan "
511
+ + " files. One added by a plugin or an extension is known only to the editor, whose scan "
512
512
  + "imports them"
513
513
  )
514
514
 
@@ -706,7 +706,7 @@ static func _md5_of_all(paths: Array[String]) -> String:
706
706
 
707
707
 
708
708
  ## The md5 of each resource's sidecar as the editor last imported it, keyed by path, from the cache
709
- ## the editor writes under .godot/editor; empty when there is no cache or it cannot be read.
709
+ ## the editor writes under .godot/editor. Empty when there is no cache or it cannot be read.
710
710
  func _imported_sidecar_md5s() -> Dictionary:
711
711
  if _sidecar_md5s != null:
712
712
  return _sidecar_md5s
@@ -716,7 +716,7 @@ func _imported_sidecar_md5s() -> Dictionary:
716
716
  var file: FileAccess = null if cache.is_empty() else FileAccess.open(cache, FileAccess.READ)
717
717
  if file == null:
718
718
  return found
719
- # The first line is the import settings version; then a "::<dir>::<time>" line opens each
719
+ # The first line is the import settings version, then a "::<dir>::<time>" line opens each
720
720
  # directory, and each file line is nine fields split by "::", the last holding the file's
721
721
  # dependencies, which may contain the splitter. The eighth field is "<>"-separated, with the
722
722
  # sidecar's md5 sixth.
@@ -765,12 +765,12 @@ static func _path_of(entry: String) -> String:
765
765
  return entry if at < 0 else entry.substr(at + 2)
766
766
 
767
767
 
768
- ## Every path [param resource_path] depends on, through the resources it depends on in turn; a set,
768
+ ## Every path [param resource_path] depends on, through the resources it depends on in turn. A set,
769
769
  ## keyed by path.
770
770
  static func _depended_on(resource_path: String) -> Dictionary[String, bool]:
771
771
  var found: Dictionary[String, bool] = {}
772
772
  var pending: Array[String] = [resource_path]
773
- # Every path is followed once, which ends the walk and survives a cycle; a cap or a list of
773
+ # Every path is followed once, which ends the walk and survives a cycle. A cap or a list of
774
774
  # extensions would leave out a material behind a long chain or in a saved .mesh.
775
775
  while not pending.is_empty():
776
776
  var next: String = pending.pop_back()
@@ -787,7 +787,7 @@ static func _shaped_by_a_script(sidecar: ConfigFile) -> bool:
787
787
  return not str(sidecar.get_value("params", "import_script/path", "")).is_empty()
788
788
 
789
789
 
790
- ## The files an import wrote, from its sidecar's [code]dest_files[/code]; empty when it lists none.
790
+ ## The files an import wrote, from its sidecar's [code]dest_files[/code]. Empty when it lists none.
791
791
  static func _outputs_of(sidecar: ConfigFile) -> Array[String]:
792
792
  var outputs: Array[String] = []
793
793
  var listed: Variant = sidecar.get_value("deps", "dest_files", [])
@@ -235,7 +235,7 @@ func add_input_action(params: Dictionary) -> Dictionary:
235
235
 
236
236
  _log.info("Adding input action: " + action_name)
237
237
  # The editor's Input Map refuses these, since project.godot keys an action by its name and
238
- # the characters below are its own syntax there; a name holding one is written escaped and
238
+ # the characters below are its own syntax there. A name holding one is written escaped and
239
239
  # reads back as a name nobody typed.
240
240
  for index: int in action_name.length():
241
241
  var code: int = action_name.unicode_at(index)
@@ -299,7 +299,7 @@ func add_input_action(params: Dictionary) -> Dictionary:
299
299
  answer["old_deadzone"] = old_deadzone
300
300
 
301
301
  # Through ProjectSettings so the file is saved the way the editor saves it, header and
302
- # every other line kept; a ConfigFile of project.godot drops the comments on the way out.
302
+ # every other line kept. A ConfigFile of project.godot drops the comments on the way out.
303
303
  ProjectSettings.set_setting(setting, action)
304
304
  var err: Error = GdhProjectFile.save([setting])
305
305
  if err != OK:
@@ -423,7 +423,7 @@ func remove_input_action(params: Dictionary) -> Dictionary:
423
423
  # The value project.godot stores for one input action.
424
424
  #
425
425
  # It has to be a Dictionary holding real InputEvent objects. The engine writes those as the
426
- # unquoted expression it parses back into an action; hand it the same thing as assembled
426
+ # unquoted expression it parses back into an action. Hand it the same thing as assembled
427
427
  # text and it writes a quoted, escaped string, which loads as a String and leaves InputMap
428
428
  # with no action at all while add_input_action still reports the events it was given.
429
429
  func build_input_action(deadzone: float, events: Array) -> Dictionary:
@@ -1,6 +1,6 @@
1
1
  extends RefCounted
2
2
 
3
- # Everything printed is diagnostics; the answer goes to a file. The server reads `[ERROR]` lines as
3
+ # Everything printed is diagnostics. The answer goes to a file. The server reads `[ERROR]` lines as
4
4
  # the operation's own errors, and the prefixes let a human read the rest without parsing it.
5
5
  #
6
6
  # Every module preloads this as `GdhLog`. `Logger` is a native class from Godot 4.5 on, and a
@@ -135,7 +135,7 @@ static func _why_not_loaded(plugin_cfg_path: String) -> String:
135
135
  var script_path: String = plugin_cfg_path.get_base_dir().path_join(script_name)
136
136
  if not FileAccess.file_exists(script_path):
137
137
  return "%s does not exist, so the editor will not enable the plugin." % script_path
138
- # Loaded only to learn whether the editor could; what the engine says of a script that does not
138
+ # Loaded only to learn whether the editor could. What the engine says of a script that does not
139
139
  # parse is about a file the answer does not describe.
140
140
  printerr(GdhLog.ASIDE)
141
141
  var script: Script = ResourceLoader.load(
@@ -189,7 +189,7 @@ func disable_plugin(params: Dictionary) -> Dictionary:
189
189
  # folder names, because the list is written back whole: reading names off a pattern for
190
190
  # `res://addons/<name>/plugin.cfg` dropped a plugin one folder down, and the next enable or disable
191
191
  # wrote the list without it, disabling a plugin nobody named. The editor stores the engine
192
- # expression PackedStringArray(...), which loads as that type; the String form is what an older
192
+ # expression PackedStringArray(...), which loads as that type. The String form is what an older
193
193
  # hand-edited file can carry.
194
194
  func _enabled_paths() -> Array[String]:
195
195
  var paths: Array[String] = []
@@ -174,7 +174,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
174
174
  % [setting_path, "setting is" if near.size() == 1 else "settings are", ", ".join(near)]
175
175
  )
176
176
  # Written as given, since a file can be made after the setting names it, as an import makes a
177
- # translation; said, since until then the engine complains of it on every boot.
177
+ # translation. Said, since until then the engine complains of it on every boot.
178
178
  var missing: Array[String] = _missing_files(final_value)
179
179
  if not missing.is_empty():
180
180
  answer["missing_files"] = missing
@@ -190,7 +190,7 @@ func set_project_setting(params: Dictionary) -> Dictionary:
190
190
  var made: Array[String] = _translations_made_by(missing)
191
191
  if not made.is_empty():
192
192
  said += (
193
- "; project_import reimport makes the translation files from " + ", ".join(made)
193
+ ". project_import reimport makes the translation files from " + ", ".join(made)
194
194
  )
195
195
  answer["note"] = (str(answer["note"]) + " " if answer.has("note") else "") + said + "."
196
196
  ProjectSettings.set_setting(setting_path, final_value)
@@ -289,7 +289,7 @@ func unset_project_setting(params: Dictionary) -> Dictionary:
289
289
  var near: PackedStringArray = PackedStringArray() if held else _settings_near(setting_path)
290
290
  if not near.is_empty():
291
291
  answer["note"] += (
292
- " Nor does the engine declare it; the nearest %s %s."
292
+ " Nor does the engine declare it. The nearest %s %s."
293
293
  % ["setting is" if near.size() == 1 else "settings are", ", ".join(near)]
294
294
  )
295
295
  return _with_what_holds(answer, setting_path)
@@ -415,7 +415,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
415
415
  (
416
416
  "An autoload cannot be added disabled: Godot loads every autoload in the list "
417
417
  + "at every start. global: false keeps its name out of the global scope and "
418
- + "still loads it; remove_autoload is what stops it loading"
418
+ + "still loads it. remove_autoload is what stops it loading"
419
419
  )
420
420
  )
421
421
  )
@@ -443,7 +443,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
443
443
  var was_updated: bool = ProjectSettings.has_setting(setting)
444
444
 
445
445
  # An asterisk in front of the path makes the autoload's name a global variable, the editor's
446
- # "Global Variable" column. Without it the node still comes up under the root; only the name
446
+ # "Global Variable" column. Without it the node still comes up under the root. Only the name
447
447
  # stops resolving.
448
448
  var entry: String = ("*" if global else "") + path
449
449
  if was_updated and str(ProjectSettings.get_setting(setting)) == entry:
@@ -455,7 +455,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
455
455
  "action": "unchanged",
456
456
  "saved": false,
457
457
  }
458
- # The editor's Add replaces an entry of the same name too; said here, since the replaced script
458
+ # The editor's Add replaces an entry of the same name too. Said here, since the replaced script
459
459
  # is no longer loaded and nothing else in the answer names it.
460
460
  var held: String = str(ProjectSettings.get_setting(setting)) if was_updated else ""
461
461
  ProjectSettings.set_setting(setting, entry)
@@ -484,7 +484,7 @@ func add_autoload(params: Dictionary) -> Dictionary:
484
484
  static func _not_an_autoload(path: String) -> String:
485
485
  var refused: String = "%s is %s, and an autoload is a scene or a script extending Node"
486
486
  if not ResourceLoader.exists(path):
487
- return (refused + "; nothing was written.") % [path, "a file Godot does not load"]
487
+ return (refused + ". Nothing was written.") % [path, "a file Godot does not load"]
488
488
  var loaded: Resource = load(path)
489
489
  if loaded is PackedScene:
490
490
  return ""
@@ -492,7 +492,7 @@ static func _not_an_autoload(path: String) -> String:
492
492
  var kind: String = "nothing Godot could load"
493
493
  if loaded != null:
494
494
  kind = "a " + loaded.get_class()
495
- return (refused + "; nothing was written.") % [path, kind]
495
+ return (refused + ". Nothing was written.") % [path, kind]
496
496
  var script: Script = loaded
497
497
  if not script.can_instantiate():
498
498
  return ""
@@ -502,7 +502,7 @@ static func _not_an_autoload(path: String) -> String:
502
502
  return (
503
503
  (
504
504
  "%s extends %s, which is not a Node, and Godot instantiates an autoload under the root "
505
- + "as a node, so every start of the game would fail to load it; nothing was written."
505
+ + "as a node, so every start of the game would fail to load it. Nothing was written."
506
506
  )
507
507
  % [path, base]
508
508
  )
@@ -530,7 +530,7 @@ static func _autoload_name_refusal(name: String) -> String:
530
530
  return ""
531
531
  return (
532
532
  (
533
- "%s cannot name an autoload: %s. Nothing was written; the editor's Autoload tab "
533
+ "%s cannot name an autoload: %s. Nothing was written. The editor's Autoload tab "
534
534
  + "refuses it too."
535
535
  )
536
536
  % [JSON.stringify(name), reason]
@@ -569,7 +569,7 @@ func list_autoloads(_params: Dictionary) -> Dictionary:
569
569
  var value: String = str(ProjectSettings.get_setting(setting))
570
570
  var path: String = value.trim_prefix("*")
571
571
 
572
- # Enabled whatever the asterisk says, because every autoload listed is loaded; the asterisk
572
+ # Enabled whatever the asterisk says, because every autoload listed is loaded. The asterisk
573
573
  # is whether its name is a global variable.
574
574
  var autoload_info: Dictionary = {
575
575
  "name": setting.trim_prefix("autoload/"),
@@ -264,7 +264,7 @@ func validate_project(params: Dictionary) -> Dictionary:
264
264
  )
265
265
 
266
266
  # A connection whose method or signal is not there loads without a word and fails only once the
267
- # game runs, on every emission or not at all; the references check sees files, not members.
267
+ # game runs, on every emission or not at all. The references check sees files, not members.
268
268
  checks_performed.append("connections")
269
269
  for entry: Dictionary in GdhSceneConnections.new(_log).broken():
270
270
  warnings.append(
@@ -75,7 +75,7 @@ static func _stepped(target: Object, name: String, at: int) -> Array:
75
75
 
76
76
 
77
77
  ## The refusal for [param name], which [param target] does not declare, when a step of it indexes a
78
- ## list [param target] holds and names no resource in it; or "".
78
+ ## list [param target] holds and names no resource in it, or "".
79
79
  static func unlisted(target: Object, name: String) -> String:
80
80
  var at: int = name.find("/")
81
81
  while at > 0:
@@ -163,7 +163,7 @@ static func held_at(target: Object, property: String) -> Resource:
163
163
  ## only once the element exists, so a TileSet with no physics layer had no
164
164
  ## physics_layer_0/collision_layer to write. A list the engine declares as an array grows through
165
165
  ## the method named after its elements, add_physics_layer for physics_layer_, given the indices of
166
- ## the lists around it; a list counted by a property, as a tile's polygons are by polygons_count,
166
+ ## the lists around it. A list counted by a property, as a tile's polygons are by polygons_count,
167
167
  ## grows by raising the count.
168
168
  static func grow(target: Object, name: String, called: String) -> String:
169
169
  var steps: PackedStringArray = name.split("/")
@@ -255,7 +255,7 @@ static func class_called(target: Object) -> String:
255
255
 
256
256
 
257
257
  ## The refusal for [param name], which [param target] does not declare, when it runs through a
258
- ## resource saved in a file other than [param home], the one [param target] is saved in; or "". The
258
+ ## resource saved in a file other than [param home], the one [param target] is saved in, or "". The
259
259
  ## write belongs in that file, since this one would be saved and that one left as it was: through a
260
260
  ## TileMapLayer's tile_set kept in a file of its own, or through the mesh of an instance's editable
261
261
  ## child, which is built into the scene the instance comes from.
@@ -20,7 +20,7 @@ const GdhRead = preload("reading.gd")
20
20
  const GdhSame = preload("same_values.gd")
21
21
  const GdhSerialisation = preload("serialisation.gd")
22
22
  const GdhShapes = preload("value_shapes.gd")
23
- # The data types a Theme names its items by, as in "Button/colors/font_color", and what each holds.
23
+ # The data types a Theme names its items by, as in `Button/colors/font_color`, and what each holds.
24
24
  const THEME_ITEMS: Dictionary[String, Array] = {
25
25
  "colors": [TYPE_COLOR, ""],
26
26
  "constants": [TYPE_INT, ""],
@@ -40,6 +40,7 @@ const HEADINGS: int = PROPERTY_USAGE_CATEGORY | PROPERTY_USAGE_GROUP | PROPERTY_
40
40
  ## Control's layout_mode values, which the engine does not expose as constants.
41
41
  const LAYOUT_MODE_POSITION: int = 0
42
42
  const LAYOUT_MODE_ANCHORS: int = 1
43
+ const LAYOUT_MODE_UNCONTROLLED: int = 3
43
44
  ## What a container sets on each child it lays out, beside the anchors and offsets.
44
45
  const PLACED_BY_A_CONTAINER: Array[String] = ["position", "size", "global_position"]
45
46
 
@@ -74,7 +75,7 @@ var _names_no_node: Dictionary = {}
74
75
  ##
75
76
  ## Three separate questions, asked in order, because a caller may say what it means in three
76
77
  ## ways: a dictionary that names its own type, a dictionary shaped like the type the property
77
- ## declares, or a bare array positional for a vector. Each is its own function; asking all three
78
+ ## declares, or a bare array positional for a vector. Each is its own function. Asking all three
78
79
  ## in one was twenty-four exits deep and impossible to follow.
79
80
  ##
80
81
  ## [param elements] is what a typed Array or Dictionary holds, as [method GdhDeclared.elements_of]
@@ -215,8 +216,8 @@ func write_all(target: Object, properties: Dictionary) -> String:
215
216
  var written_at: Dictionary[String, int] = {}
216
217
  var writes: int = 0
217
218
  # Which other property's write last changed each one already written, by name. A Gradient keeps
218
- # one offset for each colour, so offsets given after colors cut them back, and the refusal said
219
- # only that the engine had changed the colours.
219
+ # one offset for each colour, so offsets given after `colors` cut them back, and the refusal
220
+ # said only that the engine had changed the colours.
220
221
  var undone_by: Dictionary[String, String] = {}
221
222
  # A refusal of one of several leaves the others unwritten too, which a refusal naming only the
222
223
  # one it refused left a caller to guess.
@@ -349,7 +350,7 @@ func _note_undone(
349
350
  ## sizes its children and the scene keeps none of that for them, so their anchors, offsets, position
350
351
  ## and size are refused with what a child does set. A control in Position mode ignores an anchor
351
352
  ## preset, and the inspector offers anchors only in Anchors mode, so writing one puts it there
352
- ## first, as picking a preset in the editor does; alsoChanged then names layout_mode.
353
+ ## first, as picking a preset in the editor does, and alsoChanged then names layout_mode.
353
354
  func _laid_out(control: Control, name: String) -> String:
354
355
  var base: String = name.get_slice(":", 0)
355
356
  var anchoring: bool = base == "anchors_preset" or base.begins_with("anchor_")
@@ -366,8 +367,14 @@ func _laid_out(control: Control, name: String) -> String:
366
367
  )
367
368
  % [control.name, parent.name, parent.get_class(), base]
368
369
  )
369
- if anchoring and GdhRead.as_int(control.get("layout_mode")) == LAYOUT_MODE_POSITION:
370
+ var mode: int = GdhRead.as_int(control.get("layout_mode"))
371
+ if anchoring and mode == LAYOUT_MODE_POSITION:
370
372
  control.set("layout_mode", LAYOUT_MODE_ANCHORS)
373
+ # A scene's root reads as Uncontrolled but keeps the Position mode it was made with until it
374
+ # enters a tree, and the engine ignores a preset in that stored mode, so full rect read back as
375
+ # top left on every root a headless edit wrote.
376
+ elif anchoring and mode == LAYOUT_MODE_UNCONTROLLED:
377
+ control.set("layout_mode", LAYOUT_MODE_UNCONTROLLED)
371
378
  return ""
372
379
 
373
380
 
@@ -447,7 +454,7 @@ func _write(target: Object, key: String, value: Variant) -> void:
447
454
 
448
455
 
449
456
  ## The declaration the last step of [param path] would have, typed as the component it reaches, so
450
- ## a fraction for an integer vector's axis is refused as one for an int property is; or
457
+ ## a fraction for an integer vector's axis is refused as one for an int property is, or
451
458
  ## {"refused": why} for the first step that is not there.
452
459
  func _component_slot(target: Object, path: String) -> Dictionary:
453
460
  var steps: PackedStringArray = path.split(":")
@@ -487,7 +494,7 @@ func _component_slot(target: Object, path: String) -> Dictionary:
487
494
  return {
488
495
  "refused":
489
496
  (
490
- "%s.%s is %s, so it has no %s; it has %s."
497
+ "%s.%s is %s, so it has no %s. It has %s."
491
498
  % [owner, walked, sort, step, ", ".join(has)]
492
499
  ),
493
500
  }
@@ -562,7 +569,7 @@ func value_for(target: Object, declared: Dictionary, raw: Variant) -> Array:
562
569
 
563
570
 
564
571
  ## A property holding a Node is given the node the path leads to from [param target], as the
565
- ## editor's inspector gives it; the scene is saved with the path. A res:// path could never name a
572
+ ## editor's inspector gives it. The scene is saved with the path. A res:// path could never name a
566
573
  ## node, so the remedy a caller is given is a node path.
567
574
  func _node_reference(target: Object, property: String, raw: Variant) -> Array:
568
575
  if raw == null:
@@ -739,7 +746,7 @@ func colour_from(raw: Variant, named: String) -> Array:
739
746
  (
740
747
  (
741
748
  '%s is {"r": .., "g": .., "b": .., "a": ..} from 0 to 1, a optional, or an html '
742
- + 'code such as "#ff8000" or "#ff800080"; %s is neither.'
749
+ + 'code such as "#ff8000" or "#ff800080". %s is neither.'
743
750
  )
744
751
  % [named, JSON.stringify(raw)]
745
752
  ),
@@ -891,7 +898,7 @@ func _parse_resource_tag(value: Dictionary) -> Array:
891
898
  )
892
899
  return [true, null]
893
900
  # A plain Resource with a script or values of its own, as a read answers one built into a scene,
894
- # is one to build; taken as nothing, writing such a read back cleared the property.
901
+ # is one to build. Taken as nothing, writing such a read back cleared the property.
895
902
  if value.size() > 1:
896
903
  return _parse_new_resource("Resource", value)
897
904
  return [true, null]