# 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 %}` - the `None` action 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 `None` description ("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:
1. Line 9: Removed `{% if not is_continuous_mode %}` guard so `ACTION: None` is always in the format help.
2. Line 57: Qualified "needs another beat" with "NOT applicable if prior beat was a conclusion."
3. NEW Anti-Restatement Rule under ContinueConversation: explicit instruction that topic must drive *new* content, never rephrasing.
4. Lines 75-77: Replaced "you must select an action-do not select None" with "Prefer action over inaction. **However, recognize natural endings.**"
5. 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").
6. Lines 150-152: Removed `{% if not is_continuous_mode %}` guard so `None` is always listed in Available Actions.
7. Line 161: Continuous user message mentions `ACTION: None` as 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:
1. **Parser is strict** (`ActionManager.cpp:1783` "No ACTION: line found in response"). Requires literal `ACTION: ` prefix at start of line. No fuzzy matching, no fallback.
2. **Prompt format encourages the mistake.** `submodules/user_final_instructions/0750_embedded_actions.prompt:6-9` lists actions as bullets:
   ```
   **Available Actions:**
   - `OPENTRADE` - Use ONLY if ...
   - `OFFERQUEST` - ...
   ```
   The model sometimes emits the bare bullet name instead of the full `ACTION: OPENTRADE` line.

**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.)
