feat(client): throwaway NPC harness — drive /npc/speak on screen
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
client/scenes/npc_harness.tscn
Normal file
10
client/scenes/npc_harness.tscn
Normal file
@@ -0,0 +1,10 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/harness/npc_harness.gd" id="1_harness"]
|
||||
|
||||
[node name="NpcHarness" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
script = ExtResource("1_harness")
|
||||
94
client/scripts/harness/npc_harness.gd
Normal file
94
client/scripts/harness/npc_harness.gd
Normal file
@@ -0,0 +1,94 @@
|
||||
extends Control
|
||||
## Throwaway harness — the bounded-NPC loop on screen, not shipped UI. Type an
|
||||
## utterance to Fenn, see his voiced prose, watch moves apply and disposition
|
||||
## change, until he ends the conversation. Mirrors narrate_harness. Run this
|
||||
## scene directly (F6) with the proxy + Ollama up.
|
||||
|
||||
const NpcService = preload("res://scripts/net/npc_service.gd")
|
||||
const NpcContent = preload("res://scripts/npc/npc_content.gd")
|
||||
const MoveApplier = preload("res://scripts/npc/move_applier.gd")
|
||||
const ContentDB = preload("res://scripts/content/content_db.gd")
|
||||
const HttpTransport = preload("res://scripts/net/http_transport.gd")
|
||||
const FallbackLibrary = preload("res://scripts/net/fallback_library.gd")
|
||||
|
||||
const NPC_ID := "fenn"
|
||||
|
||||
var _service: NpcService
|
||||
var _content: ContentDB
|
||||
var _game_state: GameState
|
||||
var _canon_log: CanonLog
|
||||
var _output: RichTextLabel
|
||||
var _entry: LineEdit
|
||||
var _speak_btn: Button
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
var http := HTTPRequest.new()
|
||||
add_child(http)
|
||||
_service = NpcService.new(HttpTransport.new(http), FallbackLibrary.new())
|
||||
|
||||
_content = ContentDB.new()
|
||||
_content.load_from(ContentDB.default_content_root())
|
||||
|
||||
_game_state = GameState.new()
|
||||
_canon_log = CanonLog.new()
|
||||
_canon_log.set_location("greywater_docks", "Greywater Docks")
|
||||
_canon_log.player.name = "Aldric"
|
||||
_canon_log.player.class_id = "sellsword"
|
||||
# Deliberately no find_the_ledger quest here — leave it un-started so Fenn
|
||||
# can offer_quest it during the conversation.
|
||||
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
vbox.add_theme_constant_override("separation", 12)
|
||||
add_child(vbox)
|
||||
|
||||
_output = RichTextLabel.new()
|
||||
_output.bbcode_enabled = true
|
||||
_output.fit_content = true
|
||||
_output.custom_minimum_size = Vector2(600, 400)
|
||||
vbox.add_child(_output)
|
||||
|
||||
_entry = LineEdit.new()
|
||||
_entry.placeholder_text = "Say something to Fenn…"
|
||||
vbox.add_child(_entry)
|
||||
|
||||
_speak_btn = Button.new()
|
||||
_speak_btn.text = "Speak"
|
||||
_speak_btn.pressed.connect(_on_speak)
|
||||
vbox.add_child(_speak_btn)
|
||||
|
||||
|
||||
func _on_speak() -> void:
|
||||
var utterance := _entry.text
|
||||
if utterance.strip_edges() == "":
|
||||
return
|
||||
_entry.text = ""
|
||||
_speak_btn.disabled = true
|
||||
_append("[b]You:[/b] %s" % utterance)
|
||||
|
||||
var disposition := int(_game_state.npc_dispositions.get(NPC_ID, 0))
|
||||
var available := NpcContent.available_moves(_content.npc(NPC_ID), _game_state, _canon_log)
|
||||
|
||||
var r: NpcResult = await _service.speak(
|
||||
NPC_ID, utterance, _canon_log, disposition, available)
|
||||
|
||||
# Apply state explicitly (§2) — the service applied nothing.
|
||||
MoveApplier.apply(r.valid_moves, _game_state, _canon_log, _content, NPC_ID)
|
||||
for f in r.facts:
|
||||
_canon_log.add_fact(f)
|
||||
|
||||
_append("[b]Fenn:[/b] %s" % r.display_text)
|
||||
_append("[i]moves: %d applied, %d dropped · disposition %d%s[/i]" % [
|
||||
r.valid_moves.size(), r.dropped_moves.size(),
|
||||
int(_game_state.npc_dispositions.get(NPC_ID, 0)),
|
||||
" (degraded)" if r.degraded else ""])
|
||||
|
||||
if r.ends_conversation:
|
||||
_append("[i]— Fenn ends the conversation. —[/i]")
|
||||
else:
|
||||
_speak_btn.disabled = false
|
||||
|
||||
|
||||
func _append(bbcode: String) -> void:
|
||||
_output.append_text(bbcode + "\n\n")
|
||||
1
client/scripts/harness/npc_harness.gd.uid
Normal file
1
client/scripts/harness/npc_harness.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cbmr6caohwsxb
|
||||
Reference in New Issue
Block a user