gdharness 1.3.60 → 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 +2104 -1691
  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 +10 -10
  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 +10 -10
  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 +9 -9
  76. package/build/keeper.js +511 -162
  77. package/package.json +1 -1
@@ -20,14 +20,14 @@ static func menu_of(node: Node) -> PopupMenu:
20
20
  return null
21
21
 
22
22
 
23
- ## The items asked for, best first: the one at `index`; else the one whose whole words are `text`,
24
- ## exactly and then case-insensitively; else every item `text` matches by the rules `says` has, a
23
+ ## The items asked for, best first: the one at `index`, else the one whose whole words are `text`,
24
+ ## exactly and then case-insensitively, else every item `text` matches by the rules `says` has, a
25
25
  ## plain word as a contains, a glob, and alternatives split at `|`, the enabled ones when any are.
26
26
  ## Empty when nothing matches, and more than one when several match equally well, which the caller
27
27
  ## refuses rather than guesses between.
28
28
  ##
29
29
  ## By the same rules as `says` because a caller who has learnt them from a click, a find and a wait
30
- ## writes the same pattern here: "Leave*" was refused against a menu holding "Leave the hall", and
30
+ ## writes the same pattern here: "Leave*" was refused against a menu holding "Leave the shop", and
31
31
  ## the item had to be written out in full on a second call. The whole words still come first, so an
32
32
  ## item named in full is chosen over another whose words contain it.
33
33
  ##
@@ -21,7 +21,7 @@ static func read_point(params: Dictionary, x_key: String, y_key: String) -> Vari
21
21
  ## row, where the control draws it.
22
22
  ##
23
23
  ## For a control that draws its parts itself and hit-tests them in _gui_input, whose centre was all
24
- ## a click could press; in a native window, where the game's window pixels do not reach, nothing
24
+ ## a click could press. In a native window, where the game's window pixels do not reach, nothing
25
25
  ## else could press the rest.
26
26
  static func point_in(params: Dictionary, node: Node, node_path: String, has_item: bool) -> Variant:
27
27
  if not params.has("x") and not params.has("y"):
@@ -29,7 +29,7 @@ static func point_in(params: Dictionary, node: Node, node_path: String, has_item
29
29
  if node is Node3D:
30
30
  return (
31
31
  (
32
- "%s is a 3D node, clicked where it is drawn; x and y name a point in a Control's "
32
+ "%s is a 3D node, clicked where it is drawn. x and y name a point in a Control's "
33
33
  + "own space"
34
34
  )
35
35
  % node_path
@@ -70,7 +70,7 @@ static func aim_of(params: Dictionary, root: Window) -> Variant:
70
70
  ## Where [param local], in the space of the node [param aim] names, arrives: {"viewport",
71
71
  ## "position"}, the viewport the pointer reaches it through and the point in that viewport's pixels,
72
72
  ## which is what an event sent there names. A Window's space is its own pixels, as the game's window
73
- ## pixels are the root's; a canvas item's is its own, the one it draws and reads _gui_input in.
73
+ ## pixels are the root's. A canvas item's is its own, the one it draws and reads _gui_input in.
74
74
  static func carried(aim: Dictionary, local: Vector2, root: Window) -> Dictionary:
75
75
  var node: Node = aim.get("node", root)
76
76
  var reached: Dictionary = {}
@@ -192,7 +192,7 @@ static func outside_noted(
192
192
  "%s is outside %s viewport, %s in window pixels, so no control there receives"
193
193
  % [point, which, shown]
194
194
  )
195
- + " it; the game's _input and _unhandled_input still do."
195
+ + " it. The game's _input and _unhandled_input still do."
196
196
  )
197
197
  break
198
198
  return answer
@@ -270,7 +270,7 @@ static func takes_no_click(control: Control, node_path: String, paused: bool) ->
270
270
  if not control.can_process():
271
271
  var why: String = GdhTargets.why_it_does_not_process(control, node_path)
272
272
  var over: Node = GdhTargets.drawn_over(control)
273
- var cover: String = "" if over == null else "; %s is drawn over it" % over.get_path()
273
+ var cover: String = "" if over == null else ", and %s is drawn over it" % over.get_path()
274
274
  if paused:
275
275
  return (
276
276
  (
@@ -297,10 +297,10 @@ const READS_ONLY: String = (
297
297
  ## read it, "board:size()" or "inbox:keys()", listed in [constant LIST_CALLS] and
298
298
  ## [constant MAP_CALLS]. An engine method that reads takes literal arguments in the brackets,
299
299
  ## "get_theme_stylebox(\"panel\"):content_margin_top", since what a control's theme gives it is only
300
- ## reachable by naming what is asked for; see [method arguments_of].
300
+ ## reachable by naming what is asked for. See [method arguments_of].
301
301
  ##
302
302
  ## A step into a vector, a colour, a rectangle or a transform reads one of its components,
303
- ## "size:x" or "global_transform:origin:y", by the names GDScript gives them; see
303
+ ## "size:x" or "global_transform:origin:y", by the names GDScript gives them. See
304
304
  ## [method GdhSerialisation.components_of]. It answers the calls in [constant VALUE_CALLS] too,
305
305
  ## "linear_velocity:length()".
306
306
  ##
@@ -337,7 +337,7 @@ static func walk_to(node: Object, node_path: String, reaching: String) -> Dictio
337
337
  ## `{"message": ...}` saying why it names none.
338
338
  ##
339
339
  ## A colon path read from [param node], the node the call was made on, the way the method's own
340
- ## path is: "_ladder:shop:_wares:-15". One beginning with a slash starts at the node it names
340
+ ## path is: "_league:store:_items:-15". One beginning with a slash starts at the node it names
341
341
  ## instead, "/root/Main/Hud" or "/root/Main:_game:run". A res:// or uid:// path names a resource
342
342
  ## file, which is loaded the way the game loads one, so a resource the game already holds is handed
343
343
  ## over as that instance. The records a read answers a node and a filed resource with carry such a
@@ -387,7 +387,7 @@ static func object_at(root: Node, node: Node, node_path: String, given: Variant)
387
387
 
388
388
 
389
389
  ## Why [param record], a dictionary given where an object goes, names none. A resource built into a
390
- ## scene is read as its class beside its properties and has no path; the runtime builds no resource
390
+ ## scene is read as its class beside its properties and has no path. The runtime builds no resource
391
391
  ## from those, so the remedy is the file, not the path the other records are told to use.
392
392
  static func _no_path_in(record: Dictionary) -> String:
393
393
  if record.has("id"):
@@ -439,7 +439,7 @@ static func _resource_at(path: String) -> Dictionary:
439
439
  return { "message": "No resource at %s" % path }
440
440
  var loaded: Resource = load(path)
441
441
  if loaded == null:
442
- return { "message": "%s did not load; the game's output says why" % path }
442
+ return { "message": "%s did not load. The game's output says why" % path }
443
443
  return { "object": loaded }
444
444
 
445
445
 
@@ -580,7 +580,7 @@ static func can_read(holder: Variant, named: String) -> bool:
580
580
  ## counted as unclean for a read.
581
581
  ##
582
582
  ## A step written in double quotes is the key inside them, so a key holding a colon is one step:
583
- ## Game D keys its stock by namespaced ids, "stock:\"base:dew\"", and split at that colon the
583
+ ## Game D keys its stock by namespaced ids, "stock:\"base:water\"", and split at that colon the
584
584
  ## step named a key "base" the map had never had.
585
585
  static func _key_in(map: Dictionary, named: String) -> Array:
586
586
  var word: String = key_written(named)
@@ -625,8 +625,8 @@ static func step_naming(key: Variant) -> String:
625
625
 
626
626
  ## [param reaching] split into its steps at each colon, except one inside a call's brackets or a
627
627
  ## quoted argument or key: `get_node("/root/Main"):name` is two steps, and so is
628
- ## `stock:"base:dew"`; split at every colon a quoted argument holding one became a step that names
629
- ## nothing.
628
+ ## `stock:"base:water"`. Split at every colon a quoted argument holding one became a step that
629
+ ## names nothing.
630
630
  static func steps_of(reaching: String) -> PackedStringArray:
631
631
  var steps: PackedStringArray = []
632
632
  var step: String = ""
@@ -734,7 +734,7 @@ static func _arity(object: Object, method: String) -> Dictionary:
734
734
  ## A path is read, and a wait reads it every frame, so a call in one has to be a read. The engine
735
735
  ## marks its own reading methods const and `queue_free()`, `free()` and `set_name()` not, measured
736
736
  ## on 4.7.2, though it leaves some reads unmarked as well, `Engine.get_frames_drawn()` and
737
- ## `get_time_scale()` among them; so the refusal says the mark is missing, not that the method
737
+ ## `get_time_scale()` among them, so the refusal says the mark is missing, not that the method
738
738
  ## changes anything, and points at runtime_invoke call, which calls it. A game's methods carry no
739
739
  ## such mark, so one declared `-> void` is taken at its word, as doing something and answering
740
740
  ## nothing, and one left untyped is trusted. Without this a find with property "queue_free()" freed
@@ -756,7 +756,7 @@ static func call_refused(
756
756
  var scripted: Dictionary = _script_method(object, method)
757
757
  if not scripted.is_empty() and (given > 0 or least > 0):
758
758
  var why: String = (
759
- "since nothing marks one as only reading%s; runtime_invoke call takes them" % READS_ONLY
759
+ "since nothing marks one as only reading%s. runtime_invoke call takes them" % READS_ONLY
760
760
  )
761
761
  return (
762
762
  "%s.%s is the game's own and a path gives a game's method no arguments, %s"
@@ -784,7 +784,7 @@ static func call_refused(
784
784
  return ""
785
785
  if _native_flags(object, method) & METHOD_FLAG_CONST == 0:
786
786
  return (
787
- "%s.%s is not one the engine marks as only reading%s; runtime_invoke call calls it"
787
+ "%s.%s is not one the engine marks as only reading%s. runtime_invoke call calls it"
788
788
  % [called_as, method, READS_ONLY]
789
789
  )
790
790
  return ""
@@ -817,14 +817,14 @@ static func with_methods_offered(said: String, object: Object, method: String) -
817
817
  var close: PackedStringArray = GdhRead.nearest(method, PackedStringArray(every))
818
818
  if not close.is_empty():
819
819
  return (
820
- "%s; the nearest it has %s %s"
821
- % [said, "is" if close.size() == 1 else "are", ", ".join(close)]
820
+ "%s The nearest it has %s %s"
821
+ % [GdhRead.ended(said), "is" if close.size() == 1 else "are", ", ".join(close)]
822
822
  )
823
823
  if own.is_empty():
824
824
  return said
825
825
  own.sort()
826
- var offered: String = "its static functions are" if object is Script else "its script declares"
827
- return "%s; %s %s" % [said, offered, ", ".join(PackedStringArray(own))]
826
+ var offered: String = "Its static functions are" if object is Script else "Its script declares"
827
+ return "%s %s %s" % [GdhRead.ended(said), offered, ", ".join(PackedStringArray(own))]
828
828
 
829
829
 
830
830
  ## The entry a script attached to [param object] declares for [param method], or an empty one when
@@ -851,7 +851,7 @@ static func _native_flags(object: Object, method: String) -> int:
851
851
 
852
852
  ## Put [param value] where [param named] points, and answer the holder written into. Only ever
853
853
  ## called once [method can_read] agrees. Lists and maps are reference types, so writing into one
854
- ## reaches the game's own copy; a vector or a transform is a copy, and the one answered is the
854
+ ## reaches the game's own copy. A vector or a transform is a copy, and the one answered is the
855
855
  ## changed copy, for [method write_back] to put where it came from.
856
856
  static func write(holder: Variant, named: String, value: Variant) -> Variant:
857
857
  if holder is Array:
@@ -1018,7 +1018,7 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
1018
1018
  var rest: String = ""
1019
1019
  if keys.size() > 8:
1020
1020
  rest = " and %d more" % [keys.size() - 8]
1021
- return "%s has no key %s; it is keyed by %s%s" % [called_as, named, ", ".join(some), rest]
1021
+ return "%s has no key %s. It is keyed by %s%s" % [called_as, named, ", ".join(some), rest]
1022
1022
  if composite(holder):
1023
1023
  var kind: String = type_string(typeof(holder))
1024
1024
  var sort: String = ("an " if kind.left(1) in ["A", "E", "I", "O", "U"] else "a ") + kind
@@ -1036,11 +1036,11 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
1036
1036
  return (
1037
1037
  (
1038
1038
  "%s is %s, and %s() is not one of the calls a path makes on one, which read it"
1039
- + " and take no arguments: %s; its components are %s"
1039
+ + " and take no arguments: %s. Its components are %s"
1040
1040
  )
1041
1041
  % [called_as, sort, container_call, ", ".join(spelled), has]
1042
1042
  )
1043
- return "%s is %s, so it has no %s; it has %s" % [called_as, sort, named, has]
1043
+ return "%s is %s, so it has no %s. It has %s" % [called_as, sort, named, has]
1044
1044
  if not holder is Object:
1045
1045
  return (
1046
1046
  "%s is %s, which has nothing to read %s off"
@@ -1054,7 +1054,7 @@ static func nothing_there(holder: Variant, named: String, called_as: String) ->
1054
1054
  # for, and the caller is one pair of brackets away from what they meant.
1055
1055
  if object.has_method(named):
1056
1056
  return (
1057
- "%s has no property %s; it has a method of that name, which a path calls as %s()"
1057
+ "%s has no property %s. It has a method of that name, which a path calls as %s()"
1058
1058
  % [called_as, named, named]
1059
1059
  )
1060
1060
  return GdhRead.near_property("%s has no property %s" % [called_as, named], object, named)
@@ -16,7 +16,7 @@ const FIND_LIMIT: int = 100
16
16
  ## What a find can be narrowed by, which is also what it refuses to answer without. Named once so
17
17
  ## the refusal lists the same set the walk reads.
18
18
  const FILTERS: PackedStringArray = ["class", "script", "name", "group", "says"]
19
- ## The most lines one read answers with, unless asked for fewer. A screen is a few dozen; a
19
+ ## The most lines one read answers with, unless asked for fewer. A screen is a few dozen. A
20
20
  ## thousand is a tree somebody pointed this at by mistake.
21
21
  const READ_LIMIT: int = 500
22
22
  ## The most either can be asked for. A limit above it is refused rather than cut to it: the ceiling
@@ -117,7 +117,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
117
117
  var names: Array[String] = GdhSays.split_at_bars(wanted["name"])
118
118
  if not wanted["name"].is_empty() and names.is_empty():
119
119
  var bars_only: String = (
120
- 'name "%s" is only bars, so it names no node; write the names between them'
120
+ 'name "%s" is only bars, so it names no node. Write the names between them'
121
121
  )
122
122
  return { "type": "error", "message": bars_only % wanted["name"] }
123
123
 
@@ -244,7 +244,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
244
244
  . append(
245
245
  (
246
246
  (
247
- "name is matched as a glob against the whole name; %d %snode %s %s, which "
247
+ "name is matched as a glob against the whole name. %d %snode %s %s, which "
248
248
  + '"%s"%s would find'
249
249
  )
250
250
  % [
@@ -265,7 +265,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
265
265
  . append(
266
266
  (
267
267
  (
268
- "says with a wildcard is matched as a glob against a node\'s whole text; "
268
+ "says with a wildcard is matched as a glob against a node\'s whole text. "
269
269
  + '"%s"%s would find %d %snode%s'
270
270
  )
271
271
  % [
@@ -284,7 +284,7 @@ func find_nodes(params: Dictionary) -> Dictionary:
284
284
  answer["hidden"] = hidden
285
285
  notes.append(
286
286
  (
287
- "%d matching node%s hidden and left out; includeHidden true answers with them too"
287
+ "%d matching node%s hidden and left out. includeHidden true answers with them too"
288
288
  % [hidden, "" if hidden == 1 else "s"]
289
289
  )
290
290
  )
@@ -296,14 +296,14 @@ func find_nodes(params: Dictionary) -> Dictionary:
296
296
  (
297
297
  (
298
298
  "%d matching node%s drawn outside the viewport or scrolled out of a "
299
- + "ScrollContainer and left out; includeHidden true answers with them too"
299
+ + "ScrollContainer and left out. includeHidden true answers with them too"
300
300
  )
301
301
  % [out_of_view, "" if out_of_view == 1 else "s"]
302
302
  )
303
303
  )
304
304
  )
305
305
  if not notes.is_empty():
306
- answer["note"] = " ".join(notes)
306
+ answer["note"] = ". ".join(notes) + "."
307
307
  return answer
308
308
 
309
309
 
@@ -451,7 +451,7 @@ func _matches_apart_from_name(node: Node, wanted: Dictionary[String, String]) ->
451
451
  ## own class_name, or one it extends at any distance.
452
452
  ##
453
453
  ## The class_name alone answered 0 for `Card` over a tree of rows whose scripts extend Card two
454
- ## steps down, IntakeRow extends DocketCard extends Card, while `is_class` reaches every native
454
+ ## steps down, InboxRow extends TicketCard extends Card, while `is_class` reaches every native
455
455
  ## subclass. A caller naming a class means what extends it, whichever side of the line the class
456
456
  ## is declared on.
457
457
  static func _script_is(attached: Script, wanted: String) -> bool:
@@ -468,15 +468,15 @@ static func _script_is(attached: Script, wanted: String) -> bool:
468
468
  ## The question a caller asks most often, and the one that cost the most to answer: reading a panel
469
469
  ## was a find for every label with the path of each beside it, a few hundred characters apiece to
470
470
  ## carry a sentence of six words. Long is the smaller half of it. A find answers off nodes nobody
471
- ## can see, and a panel keeps its empty state in the tree beside its rows, so "Nothing posted" came
472
- ## back next to the three things posted and was believed twice in one session.
471
+ ## can see, and a panel keeps its empty state in the tree beside its rows, so "Nothing listed" came
472
+ ## back next to the three things listed and was believed twice in one session.
473
473
  ##
474
474
  ## A hidden node is left out and so is everything under it, because what a player reads is what is
475
475
  ## drawn. [param include_hidden] asks for the lot instead, which is what a caller checking that
476
476
  ## something is not showing wants.
477
477
  ##
478
478
  ## [param limit] is the first few lines rather than all of them, which is how the top of a screen
479
- ## is read without the hall under it: the bar along the top of a guild is eleven lines and the
479
+ ## is read without the rest under it: the bar along the top of a screen is eleven lines and the
480
480
  ## panel it sits on is two hundred. `omitted` says how many lines that left behind, so a screen
481
481
  ## whose interesting half is below the cut says so in a number rather than in a flag.
482
482
  func read_text(params: Dictionary) -> Dictionary:
@@ -519,7 +519,7 @@ func read_text(params: Dictionary) -> Dictionary:
519
519
  (
520
520
  (
521
521
  "%d line%s drawn outside the viewport or scrolled out of a "
522
- + "ScrollContainer, where the player cannot read them, left out; "
522
+ + "ScrollContainer, where the player cannot read them, left out. "
523
523
  + "includeHidden true answers with them too"
524
524
  )
525
525
  % [tally["out_of_view"], "" if tally["out_of_view"] == 1 else "s"]
@@ -545,7 +545,7 @@ func read_text(params: Dictionary) -> Dictionary:
545
545
  )
546
546
  )
547
547
  if not notes.is_empty():
548
- answer["note"] = " ".join(notes)
548
+ answer["note"] = ". ".join(notes) + "."
549
549
  return answer
550
550
 
551
551
 
@@ -10,11 +10,11 @@ const ESCAPED_BAR: String = ""
10
10
  ## given and the same words widened, and anything past a handful is a caller that has moved on.
11
11
  const SPLITS_KEPT: int = 8
12
12
 
13
- ## Each `says` split into alternatives, by what was written; see [method alternatives].
13
+ ## Each `says` split into alternatives, by what was written. See [method alternatives].
14
14
  static var _splits: Dictionary[String, Array] = {}
15
- ## Each `says` as the globs [method matches] tries first, by what was written; see [method _loose].
15
+ ## Each `says` as the globs [method matches] tries first, by what was written. See [method _loose].
16
16
  static var _globs: Dictionary[String, Array] = {}
17
- ## Every space separator Unicode has besides the plain space, built once; see [method spaced].
17
+ ## Every space separator Unicode has besides the plain space, built once. See [method spaced].
18
18
  static var _spaces: RegEx = null
19
19
 
20
20
 
@@ -71,7 +71,7 @@ static func _loose(wanted: String) -> Array[String]:
71
71
 
72
72
  ## [param text] with every Unicode space separator written as a plain space, which is what a caller
73
73
  ## types for any of them. A game keeping a name on one line writes it with a non-breaking space, and
74
- ## "Deskanem Bonituk" typed with a plain one found nothing while the screen showed exactly those
74
+ ## "Teodor Vasquelle" typed with a plain one found nothing while the screen showed exactly those
75
75
  ## words. One character for one, so a place found in the result is the same place in [param text].
76
76
  ##
77
77
  ## Text that is all ASCII holds none of them and is handed back as it is, which is most of a screen.
@@ -101,7 +101,7 @@ static func spaced(text: String) -> String:
101
101
  ## pattern that matches every node there is.
102
102
  ##
103
103
  ## Kept once split, since a find, a click or a wait asks about the same words for every node on the
104
- ## screen: split afresh for each of them, one look over a hall of twenty thousand nodes took 210ms
104
+ ## screen: split afresh for each of them, one look over twenty thousand nodes took 210ms
105
105
  ## rather than 155ms. The array handed back is shared and not to be changed.
106
106
  static func alternatives(wanted: String) -> Array[String]:
107
107
  var kept: Variant = _splits.get(wanted)
@@ -48,8 +48,8 @@ const WHOLE: int = 1
48
48
  ## counting from 0 in rank order.
49
49
  ##
50
50
  ## A control with something drawn over its centre that a pointer there would reach instead is not
51
- ## on screen for a player, and a click at it lands on the thing on top. The screen a game shows
52
- ## over its hall covers the hall's drawers without hiding them, so their words matched as well.
51
+ ## on screen for a player, and a click at it lands on the thing on top. A panel a game shows
52
+ ## over a screen covers its drawers without hiding them, so their words matched as well.
53
53
  static func control_saying(
54
54
  root: Node, root_path: String, wanted: String, which: Variant
55
55
  ) -> Dictionary:
@@ -60,7 +60,7 @@ static func control_saying(
60
60
  # The words of the control that matched, which a button pressed on a label's behalf has none of.
61
61
  var said: Array[String] = []
62
62
  var exact: Array[bool] = []
63
- # Whether the words are only inside longer words there, "out" in "Scout".
63
+ # Whether the words are only inside longer words there, "away" in "Faraway".
64
64
  var inside: Array[bool] = []
65
65
  var hidden: Array[Node] = []
66
66
  # Controls saying exactly the words that a click cannot reach, kept to say what a match on
@@ -150,8 +150,8 @@ static func control_saying(
150
150
  if cover == null:
151
151
  cover = over
152
152
  # Stable, so matches of one rank keep the order they are drawn in. Words only inside longer
153
- # words come after everything else: ranked as a button saying them as part of more, "Scout, 90"
154
- # was pressed for "Out" over a control saying exactly "Out", and spent a game's silver.
153
+ # words come after everything else: ranked as a button saying them as part of more, a card was
154
+ # pressed over a control saying exactly the words, and spent something in the game.
155
155
  open.sort_custom(
156
156
  func(a: int, b: int) -> bool:
157
157
  if inside[a] != inside[b]:
@@ -203,7 +203,7 @@ static func control_saying(
203
203
  "type": "error",
204
204
  "message":
205
205
  (
206
- "%s, so which to click is not clear; pass index, counting from 0%s: %s"
206
+ "%s, so which to click is not clear. Pass index, counting from 0%s: %s"
207
207
  % [_described(best, ranks[0], wanted, under), order, _candidates(targets, lines)]
208
208
  ),
209
209
  }
@@ -225,9 +225,9 @@ static func control_saying(
225
225
  "index %s picks nothing: %s: %s" % [given, among, _candidates(targets, lines)],
226
226
  }
227
227
  # A match saying the words as part of more, while another control says exactly them, is a
228
- # stand-in for that one, not the control meant: a tab reading "Out" sat in a collapsed drawer
229
- # and a dispatch saying somebody "fell out in the hall" was pressed, and with the tab's own
230
- # words on screen in a control that is not a button, a card reading "Scout, 90" was. Pressed
228
+ # stand-in for that one, not the control meant: a tab reading "Away" sat in a collapsed drawer
229
+ # and a line saying somebody "went away for good" was pressed, and with the tab's own
230
+ # words on screen in a control that is not a button, a card reading "Faraway, 90" was. Pressed
231
231
  # only when asked for by index, which is the caller saying the match is what they meant.
232
232
  var exact_elsewhere: Array[Node] = exact_out_of_reach.duplicate()
233
233
  var elsewhere_as: Array[String] = out_of_reach_as.duplicate()
@@ -250,7 +250,7 @@ static func control_saying(
250
250
  (
251
251
  (
252
252
  'no %s on screen%s says exactly "%s", so nothing was clicked: %s %s says it as '
253
- + 'part of "%s", while %s; pass index %d to press %s anyway'
253
+ + 'part of "%s", while %s. Pass index %d to press %s anyway'
254
254
  )
255
255
  % [
256
256
  "button" if exact_elsewhere.size() > exact_out_of_reach.size() else "control",
@@ -272,7 +272,7 @@ static func control_saying(
272
272
  (
273
273
  (
274
274
  'no control on screen%s says "%s" as a word of its own, so nothing was '
275
- + 'clicked: %s %s has it only inside a longer word, "%s"; pass index %d to '
275
+ + 'clicked: %s %s has it only inside a longer word, "%s". Pass index %d to '
276
276
  + "press %s anyway"
277
277
  )
278
278
  % [
@@ -287,8 +287,8 @@ static func control_saying(
287
287
  ),
288
288
  }
289
289
  # Words in a sentence on a control that lets the pointer through: the press lands on whatever is
290
- # under it, which is nothing the words name. A line saying what "is out there" was the best
291
- # match left for "Out" once the cards saying "Scout" were passed over.
290
+ # under it, which is nothing the words name. A line saying what "is far away" was the best
291
+ # match left for "Away" once the cards saying "Faraway" were passed over.
292
292
  if (
293
293
  which == null
294
294
  and not wholes[index]
@@ -301,7 +301,7 @@ static func control_saying(
301
301
  (
302
302
  'no control on screen%s that takes a click says "%s", so nothing was clicked: '
303
303
  + '%s %s says it as part of "%s" and lets the pointer through to what is under '
304
- + "it; pass index %d to click there anyway"
304
+ + "it. Pass index %d to click there anyway"
305
305
  )
306
306
  % [
307
307
  under,
@@ -325,7 +325,7 @@ static func control_saying(
325
325
  % [targets.size() - 1, _passed_over(ranks[0], ranks.slice(1), false)]
326
326
  )
327
327
  )
328
- found["picked"] = "%s; %s" % [_described(1, ranks[0], wanted, under), others]
328
+ found["picked"] = "%s, and %s" % [_described(1, ranks[0], wanted, under), others]
329
329
  if covered > 0:
330
330
  found["covered"] = covered
331
331
  var answer: Dictionary = { "path": str(targets[index].get_path()), "found": found }
@@ -377,9 +377,9 @@ static func pointer_turned_off_by(control: Control) -> Control:
377
377
  ## Whether one of [param wanted]'s alternatives is the whole of [param said], or the whole of one of
378
378
  ## its lines. A glob is matched against the whole text already, so every match of one is whole.
379
379
  ##
380
- ## A line counts because a card is a title over a description: "WARD" over "wards 3" is the card
381
- ## named WARD, and read as its words run together it said WARD only as part of more, level with
382
- ## "WARDSPITE" over its own description, and a click on WARD was refused as not clear.
380
+ ## A line counts because a card is a title over a description: "GLOW" over "glows 3" is the card
381
+ ## named GLOW, and read as its words run together it said GLOW only as part of more, level with
382
+ ## "GLOWSHADE" over its own description, and a click on GLOW was refused as not clear.
383
383
  static func _says_exactly(written: String, wanted: String) -> bool:
384
384
  var said: String = GdhSays.spaced(written)
385
385
  for words: String in GdhSays.alternatives(wanted):
@@ -397,8 +397,8 @@ static func _says_exactly(written: String, wanted: String) -> bool:
397
397
 
398
398
 
399
399
  ## Whether [param written] has the plain words [param wanted] asks for only inside longer words,
400
- ## never starting where a word starts: "out" in "Scout". Words starting a longer one count as said,
401
- ## "Recruit" in "Recruits", and so does anything matched by a pattern.
400
+ ## never starting where a word starts: "away" in "Faraway". Words starting a longer one count as
401
+ ## said, "Recruit" in "Recruits", and so does anything matched by a pattern.
402
402
  static func _inside_a_word(written: String, wanted: String) -> bool:
403
403
  var said: String = GdhSays.spaced(written).to_lower()
404
404
  var found: bool = false
@@ -477,7 +477,7 @@ static func _offered_note(from: Node, drawn: Array[Node], areas: Array[Rect2]) -
477
477
  and _cover_of(button, drawn, areas, aim) == null
478
478
  )
479
479
  var words: String = GdhWords.pressable_words(from, open, LISTED_CANDIDATES)
480
- return "" if words.is_empty() else "; the buttons on screen say %s" % words
480
+ return "" if words.is_empty() else ". The buttons on screen say %s" % words
481
481
 
482
482
 
483
483
  ## What to add to a refusal that found nothing on screen, when words matched hidden controls: how
@@ -490,9 +490,9 @@ static func _hidden_note(hidden: Array[Node]) -> String:
490
490
  for node: Node in hidden.slice(0, NAMED_HIDDEN):
491
491
  named.append("%s%s" % [node.get_path(), why_hidden(node)])
492
492
  if hidden.size() == 1:
493
- return "; 1 hidden control says it, %s" % named[0]
493
+ return ". 1 hidden control says it, %s" % named[0]
494
494
  return (
495
- "; %d hidden controls say it, among them %s and %s"
495
+ ". %d hidden controls say it, among them %s and %s"
496
496
  % [hidden.size(), ", ".join(named.slice(0, -1)), named[-1]]
497
497
  )
498
498
 
@@ -503,7 +503,7 @@ static func _outside_note(outside: int, root: Node) -> String:
503
503
  if outside == 0:
504
504
  return ""
505
505
  var note: String = (
506
- "; %d control%s it outside what the screen shows"
506
+ ". %d control%s it outside what the screen shows"
507
507
  % [outside, " says" if outside == 1 else "s say"]
508
508
  )
509
509
  var window: Window = root as Window
@@ -537,9 +537,9 @@ static func _covered_note(covered: int, cover: Node) -> String:
537
537
  if covered == 0:
538
538
  return ""
539
539
  if covered == 1:
540
- return "; 1 control says it under %s, which is drawn over it" % cover.get_path()
540
+ return ". 1 control says it under %s, which is drawn over it" % cover.get_path()
541
541
  return (
542
- "; %d controls say it under what is drawn over them, such as %s"
542
+ ". %d controls say it under what is drawn over them, such as %s"
543
543
  % [covered, cover.get_path()]
544
544
  )
545
545
 
@@ -564,8 +564,8 @@ static func drawn_over(target: Control) -> Node:
564
564
 
565
565
  ## Why [param node], which is not visible in the tree, is not, as a parenthesis to follow its path:
566
566
  ## which nodes hide it, nearest first, and what has taken its place. Reading visible off the node
567
- ## itself says true when only an ancestor is hidden, so without this a caller had nothing to unhide;
568
- ## every hidden one is named because unhiding only the nearest leaves the rest hiding it.
567
+ ## itself says true when only an ancestor is hidden, so without this a caller had nothing to unhide.
568
+ ## Every hidden one is named because unhiding only the nearest leaves the rest hiding it.
569
569
  ##
570
570
  ## What has taken its place is what a click at its centre reaches now, which is the screen a game
571
571
  ## shows instead. Without a control there, as for a 3D node, it is what is shown beside the
@@ -586,7 +586,7 @@ static func why_hidden(node: Node) -> String:
586
586
  var instead: Node = null if control == null else drawn_at(control)
587
587
  if instead != null:
588
588
  return (
589
- " (%s; a click where it would be reaches %s)"
589
+ " (%s, and a click where it would be reaches %s)"
590
590
  % [", and ".join(named), instead.get_path()]
591
591
  )
592
592
  var outermost: Node = hiding.back()
@@ -602,7 +602,7 @@ static func why_hidden(node: Node) -> String:
602
602
  ## What a click at the centre of [param target], which is hidden, would reach instead, or null.
603
603
  ##
604
604
  ## The topmost thing there that takes the pointer, whatever order it is drawn in relative to the
605
- ## target, since the target is not drawn at all; and never a node holding the target, which covers
605
+ ## target, since the target is not drawn at all, and never a node holding the target, which covers
606
606
  ## its place by containing it and is not what took it.
607
607
  static func drawn_at(target: Control) -> Node:
608
608
  var drawn: Array[Node] = _pointer_takers(target.get_tree().root)
@@ -636,20 +636,20 @@ static func _drawn_above(control: Control, other: Control) -> bool:
636
636
  return layer > other_layer or (layer == other_layer and control.is_greater_than(other))
637
637
 
638
638
 
639
- ## "; A, B and C are shown beside N" for [param beside] and the hidden node [param hidden], naming
640
- ## three at most and counting the rest.
639
+ ## `", and A, B and C are shown beside N"` for [param beside] and the hidden node [param hidden].
640
+ ## It names three at most and counts the rest.
641
641
  static func _shown_beside(beside: Array[String], hidden: Node) -> String:
642
642
  if beside.is_empty():
643
643
  return ""
644
644
  var at: String = str(hidden.get_path())
645
645
  if beside.size() == 1:
646
- return "; %s is shown beside %s" % [beside[0], at]
646
+ return ", and %s is shown beside %s" % [beside[0], at]
647
647
  var listed: Array[String] = beside.slice(0, 3)
648
648
  var rest: int = beside.size() - listed.size()
649
649
  if rest > 0:
650
- return "; %s and %d more are shown beside %s" % [", ".join(listed), rest, at]
650
+ return ", and %s and %d more are shown beside %s" % [", ".join(listed), rest, at]
651
651
  var last: String = listed.pop_back()
652
- return "; %s and %s are shown beside %s" % [", ".join(listed), last, at]
652
+ return ", and %s and %s are shown beside %s" % [", ".join(listed), last, at]
653
653
 
654
654
 
655
655
  ## The nodes whose visible is false and keeps [param node] from being drawn, nearest first. Walked
@@ -728,7 +728,7 @@ static func _pointer_takers(root: Node) -> Array[Node]:
728
728
  ## a control's however it is turned or scaled.
729
729
  ##
730
730
  ## Worked out once for a click rather than once for each match. A plain word on a screen of prose
731
- ## matches a hundred labels, and asking all four thousand controls of a hall for their transform
731
+ ## matches a hundred labels, and asking all four thousand controls on screen for their transform
732
732
  ## for each of them took a click by words 80 ms, where the matching alone takes half that.
733
733
  static func _areas_of(drawn: Array[Node]) -> Array[Rect2]:
734
734
  var areas: Array[Rect2] = []
@@ -856,7 +856,7 @@ static func _holds(control: Control, point: Vector2) -> bool:
856
856
  # A control scaled to nothing holds no point, and inverting its transform is an engine error.
857
857
  if is_zero_approx(placed.determinant()):
858
858
  return false
859
- # The rectangle, which is the engine's own answer unless a script overrides `_has_point`; the
859
+ # The rectangle, which is the engine's own answer unless a script overrides `_has_point`. The
860
860
  # engine does not offer the overridden answer to scripts.
861
861
  return Rect2(Vector2.ZERO, control.size).has_point(placed.affine_inverse() * point)
862
862
 
@@ -964,7 +964,7 @@ static func _candidates(targets: Array[Control], said: Array[String]) -> String:
964
964
 
965
965
  ## What to add to a refusal that found nothing on screen, when an open menu has an item saying the
966
966
  ## words: a menu draws its items rather than holding a control for each, so a click finds none,
967
- ## while a wait for the words finds the item. A wait met on "Leave the hall" was followed by a click
967
+ ## while a wait for the words finds the item. A wait met on "Leave the shop" was followed by a click
968
968
  ## on it refused as nothing on screen saying it, with no word of the call that chooses it.
969
969
  static func _open_menu_note(root: Node, wanted: String) -> String:
970
970
  var pending: Array[Node] = [root]
@@ -981,7 +981,7 @@ static func _open_menu_note(root: Node, wanted: String) -> String:
981
981
  )
982
982
  return (
983
983
  (
984
- '; the open menu %s has an item saying it, "%s", which a click cannot '
984
+ '. The open menu %s has an item saying it, "%s", which a click cannot '
985
985
  + 'reach: choose it with runtime_input choose, path %s and text "%s"'
986
986
  )
987
987
  % [menu.get_path(), text, named.get_path(), text]
@@ -83,7 +83,7 @@ func inject_text(params: Dictionary) -> Dictionary:
83
83
  if ended_editing(focused, before):
84
84
  answer["editing_ended"] = true
85
85
  # A control drawing its own field takes characters in its gui_input, so typing at one is not
86
- # refused; but a focused button answered that the text went into it, which read as typed.
86
+ # refused, but a focused button answered that the text went into it, which read as typed.
87
87
  if into != null and before.is_empty():
88
88
  answer["note"] = (
89
89
  (
@@ -98,7 +98,7 @@ func inject_text(params: Dictionary) -> Dictionary:
98
98
 
99
99
 
100
100
  ## What [param focused] says and whether it is being edited, read before keys go in, for
101
- ## [method field_noted] and [method ended_editing] to compare with; empty where the focus is no
101
+ ## [method field_noted] and [method ended_editing] to compare with. Empty where the focus is no
102
102
  ## field. A [TextEdit] has no editing state of its own and counts as always edited.
103
103
  static func field_before(focused: Control) -> Dictionary:
104
104
  var field: LineEdit = focused as LineEdit
@@ -333,7 +333,7 @@ func choose(params: Dictionary) -> Dictionary:
333
333
  # whatever is behind it, and before the item is looked for, because opening is when a game fills
334
334
  # a menu: one rebuilt on about_to_popup was read as it stood before, measured on 4.7.2, and the
335
335
  # index found in the old items chose another item of the new ones. An OptionButton opens its
336
- # own; a bare PopupMenu is popped where it already sits, which leaves a menu already open where
336
+ # own. A bare PopupMenu is popped where it already sits, which leaves a menu already open where
337
337
  # it is.
338
338
  var opened: bool = GdhMenus.open_the_menu(node, menu)
339
339
  await _host.get_tree().process_frame