Three callers (main_window_shell.gd, narrate_harness.gd, npc_harness.gd)
still built LogPlayer with the OLD 3-arg shape (name, class_id,
luck_descriptor) after LogPlayer.new() was migrated to the 4-arg
(name, race_id, calling_id, luck_descriptor) shape. This was invisible
to both grep and the test suite: every ctor param carries a default, so
GDScript compiles a 3-arg call against a 4-arg signature without error —
it just silently mis-binds positionally. "sellsword" landed in the
race_id slot (rejected — not a race), the luck descriptor landed in the
calling_id slot (rejected — not a calling), and luck_descriptor itself
fell back to its default "". Every field in the emitted dict came out
empty, which 422s against the canon-log schema's minLength/enum
constraints. Grepping for class_id can't see this, because there is no
class_id token left anywhere — the bug is purely positional.
Also fixes prompts.py's _article(""), which returned "an" because
Python's "" in "aeiou" is True, producing a doubled-space/wrong-article
digest line when race_id is empty. _describe_player now builds its
descriptor from whichever of race/calling are present and omits the
clause entirely when both are absent.
Adds a schema-parity guard for origin.schema.json's inlined
allowed_callings enum, which duplicated the calling roster with no
runtime check tying it to Callings.IDS. Renames leftover "class"
vocabulary in test names/comments to "calling".
Regression coverage:
- client/tests/unit/test_entities.gd: ctor populates both race_id and
calling_id; a calling passed into the race_id slot (the exact shape
of the three broken call sites) yields an all-empty row.
- api/tests/test_prompts.py: empty-race and empty-race-and-calling
digest rendering, asserting no doubled space and no dangling article.
- client/tests/unit/test_schema_parity.gd: origin.schema.json's
allowed_callings enum matches Callings.IDS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QYa9u7Kdxv5gX4AnwWexy8
73 lines
2.5 KiB
GDScript
73 lines
2.5 KiB
GDScript
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")
|
|
const ConsideringIndicator = preload("res://scripts/ui/considering_indicator.gd")
|
|
|
|
var _service: DmService
|
|
var _log: CanonLog
|
|
var _label: RichTextLabel
|
|
var _status: Label
|
|
var _button: Button
|
|
var _indicator: ConsideringIndicator
|
|
|
|
|
|
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)
|
|
|
|
_indicator = ConsideringIndicator.new()
|
|
add_child(_indicator)
|
|
|
|
|
|
func _on_narrate_pressed() -> void:
|
|
_button.disabled = true
|
|
_indicator.start(_status)
|
|
var r := await _service.narrate(_log)
|
|
_indicator.stop()
|
|
_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", "human", "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
|