class_name NarrationBook extends Control ## The permanent DM narration book (mock 2a, right panel). §2: text — it consumes ## /dm/narrate prose and never treats it as truth; only harvested [FACT] tags are ## written back to the canon log (§11). Prose-only this milestone: the numbered ## response choices and the "describe your own action" line are faithful but inert ## (no action system yet); one in-world refire affordance re-runs narration. var last_degraded: bool = false var _service: DmService var _log: CanonLog var _state: ShellState var _header: Label var _prose: RichTextLabel var _status: Label var _refire: Button var _degraded_tag: Label var _indicator: ConsideringIndicator func _ready() -> void: var col := VBoxContainer.new() col.set_anchors_preset(Control.PRESET_FULL_RECT) col.add_theme_constant_override("separation", 18) add_child(col) _header = Label.new() _header.theme_type_variation = ThemeKeys.ACCENT col.add_child(_header) _prose = RichTextLabel.new() _prose.bbcode_enabled = true _prose.fit_content = true _prose.size_flags_vertical = Control.SIZE_EXPAND_FILL _prose.custom_minimum_size = Vector2(660, 320) col.add_child(_prose) _degraded_tag = Label.new() _degraded_tag.theme_type_variation = ThemeKeys.MONO _degraded_tag.text = "THE MASTER'S VOICE COMES THIN" # shown only when degraded _degraded_tag.visible = false col.add_child(_degraded_tag) # Faithful-but-inert placeholders (charter: prose-only this milestone). var prompt := Label.new() prompt.theme_type_variation = ThemeKeys.ACCENT prompt.text = "▸ Your turn. What do you do?" col.add_child(prompt) for line in [ "1 · Pay the fence double and keep your hand where he can see it", "2 · Draw your blade — he's closer than his guards", "3 · Lie — tell him the rest is coming with your dwarf"]: var choice := Button.new() choice.theme_type_variation = ThemeKeys.PARCHMENT_BUTTON choice.text = line choice.disabled = true # inert until combat/dialogue give choices meaning col.add_child(choice) var free_text := Label.new() free_text.theme_type_variation = ThemeKeys.MONO free_text.text = "⌨ …or describe your own action" col.add_child(free_text) # The one live control: refire the narration. _refire = Button.new() _refire.theme_type_variation = ThemeKeys.PARCHMENT_BUTTON _refire.text = "▸ The Master continues…" _refire.pressed.connect(_on_refire_pressed) col.add_child(_refire) _status = Label.new() _status.theme_type_variation = ThemeKeys.MONO col.add_child(_status) _indicator = ConsideringIndicator.new() add_child(_indicator) func setup(service: DmService, log: CanonLog, state: ShellState) -> void: _service = service _log = log _state = state _header.text = "%s · %s" % [state.round_label, state.location_label] func narrate() -> void: if _service == null or _log == null: return _refire.disabled = true _indicator.start(_status) var result: NarrateResult = await _service.narrate(_log) _indicator.stop() show_prose(result) for f in result.facts: _log.add_fact(f) # §11: code applies the state write, explicitly _refire.disabled = false func show_prose(result: NarrateResult) -> void: _prose.text = result.display_text last_degraded = result.degraded _degraded_tag.visible = result.degraded func _on_refire_pressed() -> void: narrate()