11 KiB
Bugs and Fixes — Running Log
A chronological record of bugs we've found in SkyrimNet behavior, what we did about them (or chose not to do), and the latent issues that surface as a result. Entries stay even after fixes — they document why a code path looks the way it does.
How to read this log
Each entry has:
- Status:
[FIXED],[PARTIAL FIX],[KNOWN, UNFIXED],[REGRESSION],[OBSERVED, NOT REPRODUCED] - Discovered: absolute date
- Symptom: what the user/we saw
- Root cause: what's actually broken
- Fix: what we changed (or "none — see notes")
- Side effects: anything the fix exposed or changed downstream
- Files touched: absolute paths
Bug #0 — First sentence not played in TTS [FIXED]
Discovered: 2026-04-18 (early in this session) Status: Fixed by user via in-game UI before we instrumented the cause.
Symptom: The first sentence of any NPC dialogue line was generated by the LLM (visible on the web UI debug view) but never spoken out loud. Subsequent sentences played fine.
Root cause: Race in the DBVO (Dialogue Background Voice Over) pipeline. SkyrimNet routes TTS audio through vanilla Skyrim's dialogue topic system; the first audio chunk would arrive before the dialogue topic was fully "mounted," and the audio buffer dispatched into a non-listening topic — silent drop. Subsequent chunks landed on the now-live topic and played fine.
Fix: User enabled two settings via in-game UI (later persisted to YAML):
enableNPCNodeUpdates: false → true(SkyrimNet.yaml:26) — forces audio node re-binding when a new line begins, so the first buffer has a live node to attach to.dbvo.fixSubtitleDuringTTS: false → true(SkyrimNet.yaml:31) — extends subtitle (and carrier line lifetime) to cover the actual TTS audio.
Plus belt-and-braces: tts.maxSegmentWords: 24 → 4 so even residual races lose at most 4 words instead of a full sentence.
Side effects: The default-off settings have small visual costs (skin texture flicker with mods that inject textures at runtime; brief subtitle flicker). Worth the trade for this user.
Files touched: SkyrimNet.yaml (in overwrite/.../config/).
Bug #1 — Recursive farewell loop in continuous mode [REVERTED — fix preserved, see notes]
Discovered: 2026-04-18
Status: Fix was applied to gamemaster_action_selector.prompt and verified working (GM returned ACTION: None in subsequent traces). Reverted later same day because the fix exposed Bug #2 (malformed action markers were no longer being masked by the GM's retry loop). The reverted edit is preserved at mods/SkyrimNet/SKSE/Plugins/SkyrimNet/prompts/gamemaster_action_selector.prompt.edit for reference. Active prompt is back to upstream baseline. Plan: re-apply the fix paired with a sister edit to submodules/user_final_instructions/0750_embedded_actions.prompt that prevents Bug #2.
Symptom: conversation_log.log:79-106 shows Severio Pelagia delivering 5+ farewells over ~1m50s after player said "Please leave me alone":
[2026-04-17 00:53:12] Severio: As you wish, lad. I'll be on my way then. Take care of yourself out here. The roads can be dangerous...
[2026-04-17 00:53:22] Severio: Farewell, lad. May the road be kind to you. I'll be heading back to the farm now. Take care of yourself out here.
[2026-04-17 00:53:30] Severio: As you wish, lad. I'll take my leave then. The road can be treacherous...
[2026-04-17 00:53:39] Severio: I'll be on my way then, lad. Take care of yourself out here. The roads can be dangerous...
[2026-04-17 00:53:48] Severio: Muttering to myself as I turn back towards home...
[2026-04-17 00:53:55] Severio: These young'uns, always in such a hurry...
Same intent, different paraphrasings, every ~8-10 seconds.
Root cause: Structural in prompts/gamemaster_action_selector.prompt:
- Line 9 (pre-fix):
{% if not is_continuous_mode %}- ACTION: None — no action needed{% endif %}— theNoneaction was removed from the action menu when in continuous mode. - Lines 75-77 (pre-fix): "You are actively directing this scene. You must select an action—do not select None." — explicit prohibition.
- Lines 109-114 (pre-fix): the wise
Nonedescription ("a conversation just concluded naturally, silence serves the mood better") was gated to non-continuous mode only.
So in continuous-roleplay mode (which user keeps on), the GM had no syntactic way to signal "the scene is done." Its only choices were StartConversation, ContinueConversation, or fail. Combined with line 763's bias ("Shape the world actively—don't just facilitate dialogue; make things happen") and the ContinueConversation reason at line 791 ("the dialogue needs another beat to conclude naturally"), the GM was structurally pushed to keep firing ContinueConversation. Each ContinueConversation re-prompted the Dialogue agent with the prior farewell in context, and the Dialogue agent dutifully restated.
Fix: Edited prompts/gamemaster_action_selector.prompt with seven changes:
- Line 9: Removed
{% if not is_continuous_mode %}guard soACTION: Noneis always in the format help. - Line 57: Qualified "needs another beat" with "NOT applicable if prior beat was a conclusion."
- NEW Anti-Restatement Rule under ContinueConversation: explicit instruction that topic must drive new content, never rephrasing.
- Lines 75-77: Replaced "you must select an action—do not select None" with "Prefer action over inaction. However, recognize natural endings."
- NEW Concrete signals list: explicit triggers for
ACTION: None("farewell," "goodbye," "I'll be on my way," "take care," "no more words," "leave me alone"). - Lines 150-152: Removed
{% if not is_continuous_mode %}guard soNoneis always listed in Available Actions. - Line 161: Continuous user message mentions
ACTION: Noneas a valid choice.
Backup naming convention: the upstream baseline (now active again after revert) had been kept at gamemaster_action_selector.prompt.backup while the fix was active. After revert, the user renamed our edited version to gamemaster_action_selector.prompt.edit — semantically clearer than .backup (it labels "this is OUR edit," not "this is what was here before"). This .edit suffix is the convention going forward for preserving reverted-but-not-discarded edits.
Side effects: Exposed Bug #2 — the over-firing GM loop had been masking the malformed-marker bug by giving the Dialogue model multiple retries to emit a parser-compatible action line. With the loop stopped, malformed markers go through to TTS uncorrected. This is the reason for the revert — we want both fixes shipped together, not a partial regression.
Files touched: mods/SkyrimNet/SKSE/Plugins/SkyrimNet/prompts/gamemaster_action_selector.prompt.
Verification: Web UI trace screenshot at 04:43 showed three consecutive ACTION: None returns from the GM after Arcadia's OPENTRADE greeting — exactly the desired behavior (no spurious continuation).
Bug #2 — Malformed action markers spoken aloud [KNOWN, UNFIXED]
Discovered: 2026-04-18 (immediately after Bug #1 fix exposed it) Status: Known, unfixed. Was previously masked by Bug #1.
Symptom: The Dialogue model emits a bare uppercase token (e.g., OPENTRADE) at the end of its dialogue text without the required ACTION: prefix. The C++ parser ignores it as a non-match, and FilterActionLines doesn't strip it (it only strips successfully-parsed action lines). The token then falls through to TTS and gets spoken verbatim. No in-game action fires.
Concrete example (from web UI screenshot, 04:42:47):
Dialogue agent output:
"Welcome, Davies Nullshari. My name is Arcadia. How may I assist you today?
OPENTRADE"
→ Arcadia speaks: "Welcome, Davies Nullshari. My name is Arcadia. How may I assist you today? OPENTRADE."
→ No trade UI opens.
Root cause: Two-layer:
- Parser is strict (
ActionManager.cpp:1783"No ACTION: line found in response"). Requires literalACTION:prefix at start of line. No fuzzy matching, no fallback. - Prompt format encourages the mistake.
submodules/user_final_instructions/0750_embedded_actions.prompt:6-9lists actions as bullets:The model sometimes emits the bare bullet name instead of the full**Available Actions:** - `OPENTRADE` — Use ONLY if ... - `OFFERQUEST` — ...ACTION: OPENTRADEline.
Why it became visible after fixing Bug #1: The over-firing GM loop gave the Dialogue model 3-5 retries per scene. Eventually one retry would emit the marker correctly and the action would fire. With the loop stopped, the first malformed emission is the only one — no retry, no recovery.
Fix candidates (none applied):
A) Prompt-side enforcement. Edit submodules/user_final_instructions/0750_embedded_actions.prompt:6-9 to add a "WRONG vs RIGHT" example:
**CRITICAL — common mistake:**
- ✗ WRONG: "Welcome to my shop. OPENTRADE"
- ✓ RIGHT: "Welcome to my shop.\nACTION: OpenTrade"
The literal `ACTION: ` prefix at start of a new line is REQUIRED. Bare action names will NOT fire.
B) Parser-side leniency. Modify the C++ DLL to recognize bare uppercase tokens at end of dialogue as candidates. Not feasible — closed source.
C) Add an "action-marker recognition" rule to the GM so the GM sees the orphaned marker in recent dialogue and fires the corresponding action on its next tick. Brittle — adds responsibility to the wrong agent and depends on GM tick timing.
Recommendation: Option A is the durable fix. Cheap, additive, treats the cause not the symptom.
Files touched: none yet. Sister edit would land at mods/SkyrimNet/SKSE/Plugins/SkyrimNet/prompts/submodules/user_final_instructions/0750_embedded_actions.prompt.
User's stance: "all fine dear it's just a game B." — willing to live with this for now while we map the architecture systematically.
Bug #3 — OPENTRADE reflects an action-name uppercasing surprise [OBSERVED, NOT REPRODUCED]
Related to Bug #2, deserves its own note.
Observation: Papyrus-registered action names are uppercased in the action registry (OpenTrade → OPENTRADE). YAML-registered actions preserve case. So OPENTRADE IS the correct registry name when emitted via the Papyrus path — the only thing the Dialogue agent gets "wrong" is the missing ACTION: prefix.
Implication: Any prompt-side fix to bug #2 should reference ACTION: OPENTRADE (uppercase, matching the registry), not ACTION: OpenTrade. Mismatch would cause the parser to also reject the corrected form.
Verified: SkyrimNet.log:16393 ActionLibrary::RegisterPapyrusAction for action: OPENTRADE.
Pattern observation: "two bugs canceling out"
Bug #1 + Bug #2 form a classic interaction pattern:
- The visible bug (recursive farewell) was caused by Bug #1.
- Bug #2 (malformed markers) had been silently present but statistically masked by Bug #1's retry loop.
- Fixing Bug #1 exposed Bug #2.
This is worth remembering when planning future fixes: a clean fix can reveal latent bugs that were invisible while the bigger bug was running. Always consider what the broken behavior was inadvertently working around.
Future-bug log slots
(Reserved for entries we haven't discovered yet. When you land here from a future session having found a new bug, add an entry following the format above and update the README's "Last verified pass" date.)