Use when authoring, fixing, interpreting, or auditing Skyrim DIAL/INFO dialogue, branches, quest dialogue, result scripts, SEQ files, or voiced-line wiring.
Installs into .claude/skills of the current project.
Are you the author of Dialogue Authoring?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/shugokifable-dialogue-authoring)
---
name: dialogue-authoring
description: Use when authoring, fixing, interpreting, or auditing Skyrim DIAL/INFO dialogue, branches, quest dialogue, result scripts, SEQ files, or voiced-line wiring.
---
# Dialogue Authoring
## Overview
Authoring dialogue means creating `DialogTopic` (DIAL) and `DialogResponses` (INFO) records — by default in
a new plugin (originals untouched), or, with the write tools' in-place lane, straight into an existing
plugin — and doing the bookkeeping that makes them actually play. The load-bearing
truth: **a byte-valid INFO that passes xEdit but skips the Creation Kit's bookkeeping plays nothing in
game** — the exact silent-failure class houseCARL refuses (Q3). This skill drives houseCARL's tools through
the five jobs and then validates the result; the dialogue *policy* lives here, the mechanism lives in the
tools.
**What houseCARL CAN do:**
- Create topics and lines, including a topic AND all its lines in ONE call with sibling cross-links
(`housecarl_bulk_create`), or one nested line under an existing topic (`housecarl_create_record`).
- Compose a line's contents — conditions, spoken responses, prompt, speaker, `LinkTo` chain, the result-
script `VirtualMachineAdapter` — all as ordinary field writes (confirm field paths with
`mutagen-reference`).
- Compile a result (TIF) script `.psc` → `.pex` (`housecarl_compile_script`).
- Write the start-game-enabled-quest `.seq` (`housecarl_write_seq`).
- Check each voiced line for its `.fuz` on disk and report a **WILL BE SILENT** line (built into create;
re-checked by `housecarl_validate_dialogue`).
- Validate a topic's or quest's whole dialogue graph and read the load-order-winning records
(`housecarl_validate_dialogue`, `housecarl_read_record`, `housecarl_cross_plugin_query`).
**What houseCARL CANNOT do — say it, never paper over it:**
- **Evaluate `Conditions` (CTDA).** Conditions decide *when* a line fires. houseCARL **can** statically catch a
meaningful subset of **malformed** conditions — `housecarl_validate_dialogue` flags a dangling form reference,
a dead quest-alias index, an unset Run On reference, and a `GetIsID` pointed at a placed instance (it recognizes
the engine-implicit `PlayerRef` `000014`/`Player` `000007` forms, so a standard player-state gate — `HasSpell` or
an actor-value check Run On `PlayerRef` — validates clean, not false-flagged as missing) — and it can
read a condition back and **decode** it (the function, its parameters, its Run On scope —
[`references/condition-functions.md`](references/condition-functions.md)) so you can check it by eye. What it
**cannot** do is **evaluate** whether a *well-formed* condition passes — only the running game can. So a
well-formed but *wrong* condition (the wrong stage number, the wrong Run On for the intent) still silently
stops a line forever — that remains the single most common silent-dead-dialogue cause and on the author to get
right.
- **Record voice audio or verify lip-sync.** Voice presence is an on-disk file check; the audio content and
voice *acting* are out of scope.
- **Promise "this will play" from a clean structural pass.** A green validate means the wiring resolves —
not that the conditions are correct, the audio exists, or the conversation reads well.
The Skyrim-specific knowledge lives in the `references/` files — read the one your task touches:
- [`references/dialogue-flow-model.md`](references/dialogue-flow-model.md) — how DLVW/DLBR/DIAL/INFO connect
and what drives the flow (conditions, LinkTo, quest stage, PNAM, DIAL-wins-wholesale). **Read this before
authoring or auditing anything.**
- [`references/condition-functions.md`](references/condition-functions.md) — how to **decode** a condition
(CTDA): function + params + Run On (Subject vs Target), the operators and the `OR` flag, a curated table of
the dialogue/quest condition functions, and CTDA-vs-Papyrus-only. Read before reading or composing any
`Conditions`.
- [`references/dialogue-branch.md`](references/dialogue-branch.md) — the DLBR entry point: its fields and the
`TopLevel`/`Blocking`/`Exclusive` flags (one of which can silently lock an NPC out of all dialogue).
- [`references/quest-objectives-tab.md`](references/quest-objectives-tab.md) — stages vs objectives vs log
entries, and why a line gates on the stage while the journal is driven by objectives.
- [`references/seq-file-format.md`](references/seq-file-format.md) — why a start-game-enabled quest needs a
`.seq` and what the file is.
- [`references/voice-file-naming.md`](references/voice-file-naming.md) — the `.fuz`/`.lip` path template and
the override folder trap.
The condition / branch / quest references are **hand-curated** (sourced from the CK wiki + Mutagen's record
model, not the by-construction generator), so they carry a staleness duty — provenance and the re-check
checklist are dev-side and are not shipped with the skill.
## Read the flow model first
Before composing or judging a line, internalise four counter-intuitive facts from the flow model — getting
any of them wrong produces dialogue that looks right and plays wrong:
- **Conditions pick the line, not list order and not PNAM.** A line fires when its `Conditions` pass
(usually `GetStage` + a speaker check). Ordering is mostly the `Responses` list + conditions.
- **`PreviousDialog` (PNAM) is ~unused.** It is empty across effectively all vanilla content. Set it ONLY
to force a deliberate intra-topic sequence; never "complete a chain", and never read a missing PNAM as a
bug. Only a SET-but-dangling PNAM is a defect.
- **`LinkTo` is the real conversation chain** (topic → next topic), not PNAM.
- **DIAL wins wholesale.** When you override an existing topic, the winning topic's `Responses` is the whole
in-game line set — any line you don't re-list is dropped. See the editing section below.
## Player-topic semantics — where each piece of text goes
A player-choice topic can be byte-perfect — every subrecord present, `housecarl_validate_dialogue` green —
and still play absurdly, because these rules govern *what the player sees and hears*, which no data-layer
check can evaluate. They cost a shipped dialogue mod a full rework; internalise them before composing a
conversation, then reason each line through by hand.
- **In a player topic, the two text fields swap speakers.** `INFO.Prompt` is the **player's** menu line
(the button they click); `INFO.Responses` is the **NPC's** reply to it — *not* player-spoken text. Put the
prompt text into `Responses` and the NPC parrots the player's own words back.
- **`LinkTo` sets which player options appear NEXT, not what the NPC says.** The linked topic's `Name`
becomes the next menu button. Wiring an NPC reply as a `LinkTo` target produces a clickable option
literally labelled with that topic's name ("Wench reply"). The NPC's reply belongs in the current INFO's
`Responses` (one INFO can hold several `DialogResponse` rows — that *is* the multi-line-speech idiom);
`LinkTo` is only for the *player's* follow-up choices.
- **A conversation ender needs `Flags.Flags = Goodbye` on the INFO.** Without it the menu reopens after
every reply and the player can never leave the exchange. The create tools materialize the `Flags` struct
for CK-parity (job 1c) but leave `Goodbye` **unset** — it's an authoring semantic, not a default; set it
on the INFO that ends the exchange.
- **Keep an INFO's `Conditions` mutually exclusive across the topic, so intra-topic order never matters.**
Only the first condition-passing INFO in a topic plays; when two INFOs can both pass, which one wins falls
back to file group order — a fragile thing to lean on. Exclusive conditions make the outcome
order-independent, and it's *why* pure houseCARL output needs no CK ordering pass (see "Do you need to
open the Creation Kit?" below).
**Branching an NPC's reply on a single click.** A single player click evaluates every candidate INFO's
`Conditions` *once*, when the option is chosen — so an INFO's result-script fragment cannot roll a random or
branching outcome and *then* have sibling INFOs condition on it (their conditions were already tested). The
clean pattern, proven in a shipped mod: **pre-roll the outcome before the click.** Register the deciding
actor's script for the `Dialogue Menu` open (`RegisterForMenu("Dialogue Menu")`), write the result into
globals as the menu opens, then let one topic hold every outcome INFO, condition-disambiguated on those
globals — each a complete exchange. The click just selects the INFO the pre-roll already satisfied.
## The five jobs a silent INFO insert skips
| # | Job | How | Gotcha |
|---|-----|-----|--------|
| 1 | Wire topic ↔ branch ↔ quest | set `DialogTopic.Quest`/`Subtype`/`Category`; author a `DialogBranch` whose `StartingTopic` points at the topic (its entry point) | a `Custom` topic with no inbound branch or `LinkTo` is byte-valid but **never entered**; `Subtype`/`Category` enum values via `mutagen-reference` |
| 1b | SNAM subtype marker | just set `Subtype` — `housecarl_bulk_create`/`housecarl_create_record` **auto-fill** the `SubtypeName` (SNAM) marker from it (Custom→`CUST`, Hello→`HELO`, Goodbye→`GBYE`…) and report it | the game buckets topics by this 4-char marker, so a **new** topic with a `Subtype` but a **blank** marker (0000) is a **load CTD**; only set `SubtypeName` by hand to override, or when editing a topic outside the create path (then `housecarl_validate_dialogue` flags a blank one) |
| 1c | CK-parity subrecords | the create tools **auto-fill** the nullable subrecords the CK always writes but Mutagen omits, and report each — **crash-fixers:** INFO `FavorLevel` (CNAM)→`None` + `Flags` (ENAM)→empty, DLVW `DNAM`→`00` / `ENAM`→`00000000`; **byte-parity:** DLBR `Category` (TNAM)→`Player`, DIAL `Priority` (PNAM)→`50`, QUST `NextAliasID` (ANAM) + each objective's `Flags` (FNAM)→`0` | an INFO with no CNAM/ENAM **crashes the Creation Kit** when its topic is opened (the game tolerates it); a bare DLVW crashes the CK **Dialogue Views** editor. All are **non-override** — an explicit value always wins (incl. `Priority=0`, or `Category=Command` for a bribe/intimidate branch). `Branch` (BNAM) can't be auto-derived, so it stays yours — set it to the owning `DialogBranch`; `housecarl_validate_dialogue` **warns** a `Custom` topic with no BNAM (the same CK-views crash). `Goodbye` enders still need `Flags.Flags=Goodbye` set by hand |
| 2 | Order / chain the lines | `Responses` list order + `Conditions`; `LinkTo` for topic→topic | set `PreviousDialog` ONLY for a real forced sequence |
| 3 | Result (TIF) scripts | compose the line's `VirtualMachineAdapter` binding, then `housecarl_compile_script` | create-time teeth check the binding + `.pex` presence |
| 4 | SEQ for start-game-enabled quests | set the quest's Start-Game-Enabled flag, then `housecarl_write_seq` | ticking the flag alone does nothing |
| 5 | Voice | provide the `.fuz`/`.lip`; heed the WILL BE SILENT note | the folder is the **defining** plugin, not the conflict winner |
Jobs 1–2 are ordinary field writes you supply; the engine never guesses the right quest or condition for
you. Jobs 3–5 compose existing houseCARL tools. The orchestration — which jobs apply, in what order — is
this skill's job.
## Workflow — author a conversation
1. **Resolve the targets.** Identify the speaker (an NPC or a quest alias — its voice type matters for the
voice check), the quest the dialogue is gated on, and whether you need a new branch or are attaching to
an existing topic. Read existing records with `housecarl_read_record` / `housecarl_cross_plugin_query`
and confirm every field path with `mutagen-reference` before composing.
2. **Author the topic and its lines in one call** with `housecarl_bulk_create` — declare the `DialogTopic`
first, then each `DialogResponses` with `parent` naming the topic's editorid (that nests the line into
the topic's `Responses`, so a line cannot stand alone). A worked example — a player-choice topic with two
lines, the second chaining on to another topic:
```json
records=[
{ "record_type": "DialogTopic", "editorid": "MyMod_AskRing",
"operations": [
{ "field_path": "Quest", "value": "001A2B:MyMod.esp" },
{ "field_path": "Subtype", "value": "Custom" },
{ "field_path": "Name", "value": "Tell me about the ring." } ] },
{ "record_type": "DialogResponses", "editorid": "MyMod_AskRing_L1", "parent": "MyMod_AskRing",
"operations": [
{ "field_path": "Prompt", "value": "Tell me about the ring." },
{ "field_path": "Speaker", "value": "0008F2:MyMod.esp" },
{ "field_path": "Responses", "verb": "Add",
"compose": { "type": "DialogResponse",
"fields": { "Text": "It is older than this city.", "ResponseNumber": "1" } } } ] },
{ "record_type": "DialogResponses", "editorid": "MyMod_AskRing_L2", "parent": "MyMod_AskRing",
"operations": [
{ "field_path": "Speaker", "value": "0008F2:MyMod.esp" },
{ "field_path": "Responses", "verb": "Add",
"compose": { "type": "DialogResponse",
"fields": { "Text": "Take it, and be careful.", "ResponseNumber": "1" } } },
{ "field_path": "LinkTo", "verb": "Add", "value": "00C3D4:MyMod.esp" } ] }
]
```
Three points about the shape, each checked against the write surface:
- **`parent`** nests a line into its topic's `Responses` — how the one-shot says "this line belongs to
that topic."
- **A spoken row is a composed struct:** `verb:"Add"` with `compose:{ "type":"DialogResponse", "fields":{…} }`.
The `type` is required and the values sit under `fields` as **strings** (coerced server-side), so
`"ResponseNumber":"1"`, not `1`. (`DialogResponse`, singular, is the spoken-row struct; `DialogResponses`
is the INFO record.)
- **Same-call links use `@editorid`.** A record created in the same call has no FormKey yet, so reference it
as `@editorid` — valid **only** as a `Set` value on a **singular** FormLink (`Topic`, `PreviousDialog`,
`Branch`). E.g. to force L2 after L1 (the rare deliberate-sequence case — see the flow model) add
`{ "field_path":"PreviousDialog", "value":"@MyMod_AskRing_L1" }` to L2. A **list** FormLink like `LinkTo`,
and any existing or external record, takes a `XXXXXX:Plugin.esp` FormID instead — as the cross-topic
`LinkTo` above does.
The call is **all-or-nothing**: if any spec is malformed, nothing is written.
**Reachability — this example is not yet enterable.** `MyMod_AskRing` is a `Custom` topic with no entry
point, so as written it is byte-valid but the game never reaches it (only generic subtypes like Hello /
Goodbye are matched without one — see the flow model). A new player-choice menu needs a `DialogBranch`
(DLBR) whose `StartingTopic` points at the topic; author it in the **same** call, declared *after* the
topic, with `StartingTopic` set to `@MyMod_AskRing`. The reverse `DialogTopic.Branch → DLBR` back-link
can't be set in the same call (`@editorid` resolves only *earlier* siblings) — set it with a follow-up
`into=` edit if you want it, though `Branch` is usually left unset. (Adding a line to an *existing* topic
needs none of this — its entry point already exists.)
3. **Author the conditions deliberately** — the validator can check them for *malformedness* but never
*evaluate* whether a well-formed one passes. A line with no
conditions fires whenever its topic is reached; gate it with `GetStage` (quest progress) and a speaker
check (`GetIsID`/alias) as the flow model describes. Compose each `Condition` per `mutagen-reference`
(it is a polymorphic list); [`references/condition-functions.md`](references/condition-functions.md) has
the function param shapes and the Run On (Subject vs Target) scoping. Wrong conditions are the #1
silent-dead-dialogue cause — there is no tool that will catch them, so reason them through.
4. **Result scripts, if the line does something.** Compose the line's `VirtualMachineAdapter` script binding
(the `TIF_`-style fragment), author the `.psc`, and compile it with `housecarl_compile_script` (never
hand-roll `PapyrusCompiler.exe` — the tool sets the import paths and quotes spaced paths). The create-
time check flags a line whose result script isn't bound + compiled (**WILL NOT FIRE**). Use
`papyrus-reference` for function signatures.
5. **Voice.** For each voiced line, houseCARL computes the expected `.fuz` path and reports **WILL BE
SILENT** if it's absent. Provide the audio yourself (acting is out of scope). On an override, remember
the folder is the INFO's *defining* plugin, not the winner — see the voice reference.
6. **SEQ, if the quest starts at game start.** Set the quest's Start-Game-Enabled flag, then run
`housecarl_write_seq` against the plugin. Without it the quest — and all its dialogue — silently never
starts. (A plugin with no such quests needs no `.seq`; the tool reports that.) If a later **in-place** edit
or removal prunes a master, the on-disk FormIDs shift and the existing `.seq` goes stale — houseCARL flags
this in the write's read-back note so you re-run `housecarl_write_seq` (it flags, never silently rewrites).
7. **Validate, then verify.** Run `housecarl_validate_dialogue` on the topic (a DIAL FormID) or the whole
quest (a QUST FormID). It checks what it can — quest/branch wiring, `LinkTo` and PNAM resolve, voice
present, scripts bound, and every `<Global=X>` text-replacement tag backed by a global in the quest's
`TextDisplayGlobals` (an unbacked tag renders as `[...]` in game — a silent failure it now warns on) — and
**prints a standing-limits footer for what it cannot** (the CTDA conditions,
lip-sync, and the dropped-line caveat). Treat the footer as real: a clean pass is not "this will play."
Read the new records back (`full_readback` on the create call) before telling the user to enable + sort
the patch in MO2.
## Do you need to open the Creation Kit? No.
A dialogue plugin authored entirely through houseCARL plays without ever opening the CK — measured, not
asserted. A byte-diff of pure-houseCARL output against the *same* plugin after a CK open+save came to **+90
bytes = 9 INFO `PNAM` subrecords and nothing else** — the CK's intra-topic order bookkeeping. No TNAM,
TIFC, SNAM/BNAM/DNAM/ENAM/CNAM, or re-layout changed: houseCARL's CK-parity auto-fills (job 1c) already
wrote every subrecord the CK would. The reference mod ran from pure houseCARL output across multiple
in-game sessions *before* any CK save existed.
The one thing a CK save adds — the PNAM chains — only matters for a topic that depends on **first-valid-wins
ordering among overlapping conditions**, and the fix is at authoring time, not in the CK: keep the topic's
INFO `Conditions` mutually exclusive (above), or set `PreviousDialog` (PNAM) chains yourself —
houseCARL's `@editorid` sibling links set a forced intra-topic sequence in one call. So the CK is never
*required*; reach for it only if you specifically want the editor's flowchart view, knowing houseCARL has
already written the bytes.
## Editing an existing topic — the dropped-line trap
Because DIAL wins wholesale, overriding a vanilla or modded topic to add one line means your override
becomes the authoritative line set — and any line the original topic had that you don't re-list is **dropped
in game**. So when extending an existing topic, carry forward every line it should still have, not just your
new one. `housecarl_validate_dialogue` validates the *winning* topic's `Responses` and warns about this,
but a record-only glance won't show the loss. This is the classic "two mods touched one topic and lines
vanished" conflict.
**The in-place lane sidesteps this trap.** If the topic lives in a plugin you own (or are willing to edit
directly), the write tools' in-place lane (`target=<plugin>`, `in_place=true`, `acknowledge=`) edits the
original DIAL/INFO records instead of authoring an override — so there is no override line-set to keep
complete and no line can be dropped. The override lane above (the default, originals untouched) is still the
right choice for patching a *third-party* plugin you don't want to rewrite — there you must carry forward
every line the topic should keep.
## Write-side recipes — clone a condition gate, write a CK-refused subtype
Three repeatedly-needed edits to *existing* dialogue ride the write tools you already have —
`housecarl_bulk_apply` and `housecarl_set_field` — each with one sharp edge worth stating once.
**Recipe A — clone a verified condition gate onto N empty Infos. NEVER hand-synthesize the operator bytes.**
A `Condition` (CTDA) is a polymorphic struct — a `ConditionFloat` carrying a `CompareOperator`, a
`ComparisonValue`, and a polymorphic `Data` (the function + its params). *Computing* that encoded
operator/comparison by hand is exactly what once wrote 26 broken conditions onto one gate. So don't — **read
a known-good gate back and replay its rows verbatim**:
1. Build the gate once (in CK, or on one Info you've validated) and read it back with `housecarl_read_record`
(`Conditions`, deep). That array is your source of truth — every field below is **copied, nothing computed**.
2. For each target Info, **read it first and skip any that already carry `Conditions`** — there is no
idempotent verb, so the read-then-skip is yours to do, and it is what makes a re-run safe.
3. Replay each source row as a composed `Add` into the target's `Conditions`. One `Add` per row; the
polymorphic element composes by its concrete arm, with `Data` composed by *its* arm:
```json
operations=[
{ "formid": "0A12C4:MyMod.esp", "field_path": "Conditions", "verb": "Add",
"compose": { "type": "ConditionFloat",
"fields": { "CompareOperator": "GreaterThanOrEqualTo", "ComparisonValue": "20" },
"sets": [ { "path": "Data",
"compose": { "type": "GetStageConditionData",
"fields": { "Quest": "001234:MyMod.esp", "RunOnType": "Subject" } } } ] }
}
// ...one more Add per source row — arm type, CompareOperator, ComparisonValue, the Data arm + its
// params copied verbatim from the read-back; confirm arm/field names via mutagen-reference...
]
```
Pass `full_readback=true` and confirm the written rows match the source before enabling the patch.
(Conditions-only edits do **not** need a `.seq` regen.)
**Recipe B — write an INFO subtype CK's dropdown refuses to offer.** CK's player-dialogue subtype dropdown
only lists subtypes already present in the branch, so you cannot pick e.g. `ForceGreet` there. The subtype
lives on the **topic, not the line** — it is `DialogTopic.Subtype` (the DIAL); `DialogResponses` (the INFO)
has no `Subtype` field. Copy the exact value from a known-good ForceGreet topic and write it with
`housecarl_set_field`:
```json
housecarl_set_field( formid="0B77E0:MyMod.esp", field_path="Subtype", value="ForceGreet" )
```
(`ForceGreet` is the Mutagen spelling of xEdit's `PFGT` subtype — confirm the enum value in
`mutagen-reference`.) The write is non-destructive: it lands in a reviewable patch; read it back before
enabling + sorting in MO2.
**Recipe C — un-bind a result-script fragment from an INFO.** Clearing a fragment binding is a supported
`Remove` now — no `remove_record` + recreate. `Remove` the whole result-script adapter:
```json
housecarl_set_field( formid="0A12C4:MyMod.esp", field_path="VirtualMachineAdapter", verb="Remove" )
```
That nulls the entire `VirtualMachineAdapter` (all scripts + fragments) on the INFO. To drop only the
fragment binding while keeping any attached scripts, `Remove` the fragment field itself
(`field_path="VirtualMachineAdapter.ScriptFragments"`). Both are nullable-field clears the write engine
allows by construction (the adapter is a nullable substruct, `ScriptFragments` a nullable polymorphic
field); an explicit non-nullable/required field would refuse a `Remove` instead. Read back to confirm the
binding is gone before enabling the patch.
## Common mistakes
- **Building a PNAM chain, or flagging a missing one.** Vanilla topics have empty PNAM; ordering is the
`Responses` list + conditions. Set `PreviousDialog` only for a genuine forced sequence; never add one to
"fix" a topic, and never report its absence as a defect.
- **Forgetting the SEQ.** A Start-Game-Enabled quest with no `.seq` never starts, and neither does its
dialogue. Ticking the flag is half the job — write the `.seq`.
- **Reading a clean validate as "it'll play."** A green validate catches *malformed* conditions but never
proves a *well-formed* one is *correct* — a wrong `GetStage` value passes validation and is silent in game.
Always carry the standing-limits footer to the user.
- **Computing the voice folder from the conflict winner.** It is the plugin that *defines* the INFO. For a
new plugin that's yours (clean); for an override it's the original's folder, where the audio lives.
- **Putting the player's line in `Responses`, or an NPC reply behind `LinkTo`.** In a player topic `Prompt`
is the player's menu button and `Responses` is the NPC's reply; `LinkTo` sets the *next player options*,
not what the NPC says. Getting this backwards is byte-valid and plays absurdly — see the player-topic
semantics section.
- **Overriding a topic and dropping its other lines** (the DIAL-wins-wholesale trap above).
- **Hand-synthesizing CTDA operator/comparison bytes** instead of cloning a verified `Conditions` array
verbatim — computing the encoded operator once wrote 26 broken conditions. Read a good gate back with
`housecarl_read_record` and replay its rows (the write-side recipe above).
- **Hand-rolling the Papyrus compile** instead of `housecarl_compile_script` — hand-rolled calls mangle
spaced paths and can hit originals; the tool quotes them and lands a reviewable `.pex`.
- **Reaching for this skill when the user means distribution or a field edit.** Distributing a form to NPCs
is `spid-authoring`; a keyword onto items is `kid-authoring`; editing a record's own fields is
`skypatcher-authoring`. This skill authors the dialogue records.
## Notes
- **Field names and enums via `mutagen-reference`.** `DialogTopic.Subtype`/`Category` are enums and
`Conditions`/`Responses` are composed lists — confirm spellings and legal values there, don't guess.
- **Quest scaffolding rides along.** A flat `QUST` and its stages/aliases/objectives are createable today
with `housecarl_create_record`/`housecarl_bulk_create`; this skill is the dialogue layer that wires onto
it. Set the quest up first, then author the topics that reference it.
- **Result-script review.** For the TIF fragment's Papyrus, `papyrus-reference` has the signatures and
`papyrus-optimization` grades the script — a result script that stack-dumps is its own silent failure.
- **Out of lane.** Exterior-cell-keyed placement and runtime-spawned (`FFxxxxxx`) speakers are separate
capabilities, not dialogue authoring — name the limit rather than guessing a path.