From 89afd85bb254049682f1be3f1f3a8b6caa26dd6b Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Fri, 10 Jul 2026 10:12:21 -0500 Subject: [PATCH] feat(client): throwaway narrate harness scene + live-smoke doc Co-Authored-By: Claude Opus 4.8 (1M context) --- client/docs/README.md | 18 +++++ client/scenes/narrate_harness.tscn | 10 +++ client/scripts/harness/narrate_harness.gd | 66 +++++++++++++++++++ client/scripts/harness/narrate_harness.gd.uid | 1 + 4 files changed, 95 insertions(+) create mode 100644 client/scenes/narrate_harness.tscn create mode 100644 client/scripts/harness/narrate_harness.gd create mode 100644 client/scripts/harness/narrate_harness.gd.uid diff --git a/client/docs/README.md b/client/docs/README.md index d9e6a5f..597233b 100644 --- a/client/docs/README.md +++ b/client/docs/README.md @@ -11,3 +11,21 @@ Put here: - HTTP client + fallback-degradation handling (§13) Cross-cutting design (anything touching both client and api) goes in the root [`/docs`](../../docs), not here. + +## Narrate live smoke (M2 client HTTP loop) + +The `narrate_harness` scene is a throwaway proof of the client → `/dm/narrate` +loop. It is not the game UI. + +**Prereqs:** the proxy running and Ollama reachable. + +1. Start the proxy: from `api/`, `uvicorn app.main:app --port 8000` (with the + `.env` from `.env.example`; Ollama up at `OLLAMA_BASE_URL`). +2. In the Godot editor, open `res://scenes/narrate_harness.tscn` and run it (F6). +3. Click **Narrate this scene**. Expect grounded prose in the Narrator's voice + (referencing Greywater / Brannoc / the washed-out bridge), tags stripped, and + `facts harvested: N` with N ≥ 0. +4. **Degraded path:** stop the proxy and click again. Expect the authored + fallback line and `(degraded)` in the status — never an error. + +Override the proxy URL with the `coc_rpg/proxy_base_url` project setting. diff --git a/client/scenes/narrate_harness.tscn b/client/scenes/narrate_harness.tscn new file mode 100644 index 0000000..d4b3ec4 --- /dev/null +++ b/client/scenes/narrate_harness.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/harness/narrate_harness.gd" id="1_harness"] + +[node name="NarrateHarness" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource("1_harness") diff --git a/client/scripts/harness/narrate_harness.gd b/client/scripts/harness/narrate_harness.gd new file mode 100644 index 0000000..1ac97d9 --- /dev/null +++ b/client/scripts/harness/narrate_harness.gd @@ -0,0 +1,66 @@ +extends Control +## Throwaway M2 harness — the first on-screen generated prose. Wires the real +## transport to a button and a label. NOT the game UI (a later wireframe owns +## that). Run this scene directly (F6) with the proxy + Ollama up; see the live +## smoke steps in client/docs/README.md. + +const DmService = preload("res://scripts/net/dm_service.gd") +const HttpTransport = preload("res://scripts/net/http_transport.gd") +const FallbackLibrary = preload("res://scripts/net/fallback_library.gd") + +var _service: DmService +var _log: CanonLog +var _label: RichTextLabel +var _status: Label +var _button: Button + + +func _ready() -> void: + var http := HTTPRequest.new() + add_child(http) + _service = DmService.new(HttpTransport.new(http), FallbackLibrary.new()) + _log = _build_scene_log() + + var vbox := VBoxContainer.new() + vbox.set_anchors_preset(Control.PRESET_FULL_RECT) + vbox.add_theme_constant_override("separation", 12) + add_child(vbox) + + _button = Button.new() + _button.text = "Narrate this scene" + _button.pressed.connect(_on_narrate_pressed) + vbox.add_child(_button) + + _label = RichTextLabel.new() + _label.fit_content = true + _label.custom_minimum_size = Vector2(640, 320) + vbox.add_child(_label) + + _status = Label.new() + vbox.add_child(_status) + + +func _on_narrate_pressed() -> void: + _button.disabled = true + _status.text = "…narrating…" + var r := await _service.narrate(_log) + _label.text = r.display_text + for f in r.facts: + _log.add_fact(f) + _status.text = "facts harvested: %d%s" % [r.facts.size(), " (degraded)" if r.degraded else ""] + _button.disabled = false + + +func _build_scene_log() -> CanonLog: + var log := CanonLog.new() + log.player = LogPlayer.new("Aldric", "sellsword", "Fortune spits on you") + log.set_location("greywater_docks", "the Greywater docks") + log.party.append(PartyMember.new("brannoc_thane", "Brannoc Thane", 40)) + log.party.append(PartyMember.new("cadwyn_vell", "Cadwyn Vell", 15)) + log.push_event("Arrived at Greywater by barge before dawn") + log.push_event("Brannoc recognised the harbourmaster and went quiet") + log.add_fact("the eastern bridge out of Greywater is washed out") + log.add_fact("the harbourmaster is named Oda Fenn") + log.active_quests.append(Quest.new("find_the_ledger", "The Missing Ledger", "active", "Find who took Fenn's ledger")) + log.add_humiliation("h_0001", "vomited on a shrine step in front of a priest", 7, 3) + return log diff --git a/client/scripts/harness/narrate_harness.gd.uid b/client/scripts/harness/narrate_harness.gd.uid new file mode 100644 index 0000000..eda8b4d --- /dev/null +++ b/client/scripts/harness/narrate_harness.gd.uid @@ -0,0 +1 @@ +uid://dxh7t3rdoqgq5