[Game Systems] Schema-targeted stat-block generation (upgrade of #130) #142
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Motivation/Context
#130 ("One-click 'Flesh out' NPC backstory and stat block", v3.7.0) builds a
generate_statblockdraft mode that is deliberately system-agnostic: "LLMfree-form guided by the
game_systemhint," storing results inbodymarkdown plusloose
sidebar_fieldsrows. #130's own text names this investigation as the intendedfollow-on: "If game-aware work lands, the stat-block generator can target a real
schema. Storing stats as structured
sidebar_fieldsis chosen partly to smooth thattransition." With the versioned schema + validated
statsenvelope from #139 inplace, this issue delivers that upgrade: when a campaign has a linked system with an
active schema,
generate_statblocktargets it directly instead of producing free-formprose to parse later.
Sequencing note (per the report, §3.3): #130 should ship system-agnostic first —
its draft-rail plumbing (generating→ready→failed lifecycle, review/iterate/approve UI,
versioning) is the reusable part. This issue is a bolt-on, not a redesign of #130.
Approach
When the campaign has a linked
game_system_idwith an active schema for the entry'sLoreType,generate_statblockmode (added by #130 inwebapp/backend/app/services/audio_service.py, modeled onrephrase_lore_entry_body~line 717) passes the schema's field list into
generate_structured_text(…, json_mode=True)(audio_service.py:1158) and asks the LLM for avaluesobjectmatching the schema's field keys/types/groups — rather than free-form markdown-only
output. The response is validated against the schema (via the validator service from
#139) before entering the draft; a failing validation retries once with the error
appended as guidance, then falls back to #130's current free-form behaviour if it still
doesn't validate.
On approval, write both:
valuesinto the entry'sstatsenvelope (per #139's storagedesign), and
sidebar_fieldsoutputs #130 already produces, so thehuman-readable stat block in
bodystays in sync with the structured data (per#130's "coherence requirement" — stats must reflect the backstory, and now also the
schema).
Fallback path: campaigns with no linked system, or a linked system with no active
schema (e.g.
generic), or a schema that fails to validate after retry, get exactly#130's existing free-form behaviour unchanged. This upgrade is strictly additive on top
of #130 — no regression for free-form campaigns.
Draft review UX (old-vs-new diff, inline edit, iterate-with-feedback, approve/discard)
is #130's existing rail and is not rebuilt here; this issue only changes what the
generation step produces and validates when a schema is available.
Dependencies
this issue upgrades its
generate_statblockmode rather than building it fromscratch.
statsstorage + validation service) — provides theschema definitions to target and the validator to check the LLM's output against.
Out of scope
convert_statsmode (issue 7) — that backfills existingentries; this issue is about newly generated stat blocks.
Acceptance criteria
a draft whose structured output validates against that schema's current version.
statsenvelope is written with the correctsystemkeyand
schema_version, andbody/sidebar_fieldsare updated exactly as #130specifies today (markdown + infobox rows).
generic), or avalidation failure that survives one retry, generation falls back to #130's current
free-form behaviour with no visible regression.
cover the schema-targeted path (successful validation, one retry-then-fallback case).
coherence requirement), now also passing the schema field list.
Picking this up on
feat/142-schema-targeted-statblock(PR to target thefeat/v3.9-game-aware-systemsintegration branch). #130 (free-form statblock) and #139 (schemas +stats) are both merged into the base.Approach: when a campaign links a system whose active schema covers the entry's type, the existing
generate_statblockdraft targets that schema —generate_structured_text(json_mode=True)for a schema-conformingvaluesobject, validated via #139's validator (retry once with the error as guidance, then fall back to #130's free-form path). The proposedstatsenvelope is staged on the draft (newlore_entry_drafts.current_statscolumn) and written toentry.statson approval, alongside #130's markdown/sidebar_fields (derived from the same values for coherence). Also folds in #137's flagged follow-up — the statblock/backstory prompts switch toresolve_system_prompt_context. No-schema/generic campaigns keep #130's behaviour unchanged. No bot API change.Done and verified — merged into the integration branch via PR #210.
Verification (Docker, py3.12): 641 backend tests pass (+9 new in
test_statblock_schema_targeting.py), no regressions. Migrationd8e9f0a1b2c3up/down/up clean;current_statscolumn added.Delivered: schema-targeted
generate_statblock(generate_lore_entry_statblock_targeted→ schema-conformingvalues, validate-retry-then-fallback to #130's free-form path, sidebar/body derived from the same values for coherence); the proposed envelope staged onlore_entry_drafts.current_statsand written toentry.statson approval (re-validated against the current schema). No-schema/generic campaigns keep #130's behaviour unchanged.Also resolved #137's flagged follow-up — the flesh-out backstory/statblock prompts now use
resolve_system_prompt_context.Follow-up flagged:
patch_draftdoesn't yet acceptcurrent_statsedits during review (a GM editing a staged sidebar row won't retro-sync the structured envelope) — small follow-up if inline stat editing is wanted.Closing; ships to
mainwith the v3.9.0 release.