diff --git a/client/scripts/ui/considering_indicator.gd b/client/scripts/ui/considering_indicator.gd new file mode 100644 index 0000000..ee531ed --- /dev/null +++ b/client/scripts/ui/considering_indicator.gd @@ -0,0 +1,44 @@ +class_name ConsideringIndicator +extends Node +## Thin node shim (charter ยง13). While a call is in flight it rotates an authored +## ConsideringPhrases line onto a Label every ROTATE_SECONDS, reframing the wait +## as the GM thinking. The phrase logic is the pure, tested ConsideringPhrases; +## the Timer/tree wiring here is the untested shim (verified by running the +## harness). One instance persists on the harness so the rotation index is not +## reset per call (spec decision 4). + +const ROTATE_SECONDS := 2.0 + +var _phrases: ConsideringPhrases +var _timer: Timer +var _label: Label + + +func _init(phrases: ConsideringPhrases = null) -> void: + _phrases = phrases if phrases != null else ConsideringPhrases.new() + + +func _ready() -> void: + _timer = Timer.new() + _timer.wait_time = ROTATE_SECONDS + _timer.one_shot = false + _timer.timeout.connect(_advance) + add_child(_timer) + + +func start(label: Label) -> void: + _label = label + _advance() # show a phrase immediately, don't wait ROTATE_SECONDS + _timer.start() + + +func stop() -> void: + _timer.stop() + if _label != null: + _label.text = "" + _label = null + + +func _advance() -> void: + if _label != null: + _label.text = _phrases.next() diff --git a/client/tests/unit/test_considering_indicator.gd b/client/tests/unit/test_considering_indicator.gd new file mode 100644 index 0000000..e3e3bc5 --- /dev/null +++ b/client/tests/unit/test_considering_indicator.gd @@ -0,0 +1,19 @@ +extends "res://addons/gut/test.gd" + +const ConsideringIndicator = preload("res://scripts/ui/considering_indicator.gd") +const ConsideringPhrases = preload("res://scripts/ui/considering_phrases.gd") + + +func test_start_shows_first_phrase_and_stop_clears(): + # Point phrases at a missing file so next() is deterministically LAST_RESORT. + var phrases = ConsideringPhrases.new("res://content/does_not_exist.json") + var ind = ConsideringIndicator.new(phrases) + add_child_autofree(ind) # _ready() creates the Timer child + var label := Label.new() + add_child_autofree(label) + + ind.start(label) + assert_eq(label.text, ConsideringPhrases.LAST_RESORT) # first phrase shown at once + + ind.stop() + assert_eq(label.text, "") # cleared on stop