Clean em-dashes and smart quotes from markdown files
This commit is contained in:
@@ -8,13 +8,13 @@ A persistent reference for understanding the SkyrimNet SKSE plugin and its surro
|
||||
|
||||
## Why this archive exists
|
||||
|
||||
Earlier sessions iterated on SkyrimNet bug fixes reactively — fix one symptom, expose the next. We hit a coupling bug (gamemaster loop fix → broke action firing for malformed `OPENTRADE` markers) and decided to stop playing whack-a-bug. This archive is the systematic map we build on top, so future-Chrysalis can load context in one read and make informed structural changes instead of guessing.
|
||||
Earlier sessions iterated on SkyrimNet bug fixes reactively - fix one symptom, expose the next. We hit a coupling bug (gamemaster loop fix → broke action firing for malformed `OPENTRADE` markers) and decided to stop playing whack-a-bug. This archive is the systematic map we build on top, so future-Chrysalis can load context in one read and make informed structural changes instead of guessing.
|
||||
|
||||
## How to use this archive
|
||||
|
||||
1. **Load order:** start with `architecture.md` for the big picture, then jump to the file relevant to your task.
|
||||
2. **Trust tags:** every claim is tagged `[verified]`, `[hypothesis]`, or `[unknown]`. Don't promote a hypothesis without re-verifying.
|
||||
3. **Citations:** `path/to/file.ext:line_number` references are how we trace claims. If a path looks stale, it probably is — re-verify rather than assume.
|
||||
3. **Citations:** `path/to/file.ext:line_number` references are how we trace claims. If a path looks stale, it probably is - re-verify rather than assume.
|
||||
4. **When you fix or learn something new:** update `bugs-and-fixes.md` AND the relevant detail file, AND bump the "Last verified pass" date here.
|
||||
5. **Don't assume continuity from chat history.** This archive is the source of truth across sessions; conversation context is ephemeral.
|
||||
|
||||
@@ -28,20 +28,20 @@ Earlier sessions iterated on SkyrimNet bug fixes reactively — fix one symptom,
|
||||
| [`action-system.md`](action-system.md) | The three registration paths, ACTION: parser, two action selectors, drilldown |
|
||||
| [`config-knobs.md`](config-knobs.md) | Load-bearing YAML settings → code behavior mapping |
|
||||
| [`bugs-and-fixes.md`](bugs-and-fixes.md) | Running log of bugs we've found and what we did (or didn't do) about them |
|
||||
| [`open-questions.md`](open-questions.md) | Things still uncertain — needing in-game test or DLL disassembly |
|
||||
| [`open-questions.md`](open-questions.md) | Things still uncertain - needing in-game test or DLL disassembly |
|
||||
|
||||
## Adjacent resources in this directory
|
||||
|
||||
- `logs/` — captured `openrouter_input.log`, `openrouter_output.log`, `conversation_log.log`, `SkyrimNet.log`, `all_traces_*.json`. Use these to verify behavior claims against real traces.
|
||||
- `SkyrimNet-GamePlugin-main/` — upstream git pull of the open-source side (Papyrus + .esp + headers). The C++ DLL is closed-source, not in this pull.
|
||||
- `logs/` - captured `openrouter_input.log`, `openrouter_output.log`, `conversation_log.log`, `SkyrimNet.log`, `all_traces_*.json`. Use these to verify behavior claims against real traces.
|
||||
- `SkyrimNet-GamePlugin-main/` - upstream git pull of the open-source side (Papyrus + .esp + headers). The C++ DLL is closed-source, not in this pull.
|
||||
|
||||
## State of knowledge — quick summary
|
||||
## State of knowledge - quick summary
|
||||
|
||||
**[verified]** — agent flow architecture, three action registration paths, three-layer prompt override, Inja syntax, action parser grammar, the two action selectors and their relationship, GM loop bug + fix.
|
||||
**[verified]** - agent flow architecture, three action registration paths, three-layer prompt override, Inja syntax, action parser grammar, the two action selectors and their relationship, GM loop bug + fix.
|
||||
|
||||
**[hypothesis]** — exact firing cadence of the GM in continuous mode (appears to be a polling timer ~30s but couldn't isolate the source), `defaults_manifest.json` role (looks like first-run seed schema), whether the native_action_selector always fires after every dialogue response or only conditionally.
|
||||
**[hypothesis]** - exact firing cadence of the GM in continuous mode (appears to be a polling timer ~30s but couldn't isolate the source), `defaults_manifest.json` role (looks like first-run seed schema), whether the native_action_selector always fires after every dialogue response or only conditionally.
|
||||
|
||||
**[unknown]** — full decorator list with signatures (dynamically generated by DLL), what `agentEnabled` vs `enabled` toggle in the gamemaster block actually does differently, whether `gamemaster_scene_planner` ever fires in current config.
|
||||
**[unknown]** - full decorator list with signatures (dynamically generated by DLL), what `agentEnabled` vs `enabled` toggle in the gamemaster block actually does differently, whether `gamemaster_scene_planner` ever fires in current config.
|
||||
|
||||
## Conventions
|
||||
|
||||
@@ -51,4 +51,4 @@ Earlier sessions iterated on SkyrimNet bug fixes reactively — fix one symptom,
|
||||
|
||||
---
|
||||
|
||||
**Philosophy:** *This archive grows by addition and refinement, not by overwrite. Bug-and-fix entries stay even after the bug is gone — they're the only record of why a code path looks the way it does.*
|
||||
**Philosophy:** *This archive grows by addition and refinement, not by overwrite. Bug-and-fix entries stay even after the bug is gone - they're the only record of why a code path looks the way it does.*
|
||||
|
||||
@@ -12,7 +12,7 @@ A mod's Papyrus script calls:
|
||||
|
||||
```papyrus
|
||||
SkyrimNetApi.RegisterAction(
|
||||
string actionName, ; UPPERCASED in the registry — "OpenTrade" → "OPENTRADE"
|
||||
string actionName, ; UPPERCASED in the registry - "OpenTrade" → "OPENTRADE"
|
||||
string actionDescription,
|
||||
string eligibilityScriptName,
|
||||
string eligibilityFunctionName,
|
||||
@@ -43,7 +43,7 @@ A mod can drop YAML files into `SKSE/Plugins/SkyrimNet/config/actions/*.yaml` *i
|
||||
|
||||
**YAML actions preserve case** in the registry (unlike Papyrus actions). So `OpenTrade` stays `OpenTrade`.
|
||||
|
||||
**Schema** — sample from `SeverActions - SkyrimNet Action Pack/.../config/actions/attacktarget.yaml:1-37`:
|
||||
**Schema** - sample from `SeverActions - SkyrimNet Action Pack/.../config/actions/attacktarget.yaml:1-37`:
|
||||
```yaml
|
||||
customCategory: Combat
|
||||
name: AttackTarget
|
||||
@@ -64,9 +64,9 @@ eligibilityRules:
|
||||
```
|
||||
|
||||
**Contributing mods at last load** `[verified]` from `SkyrimNet.log`:
|
||||
- **`SeverActions - SkyrimNet Action Pack`** — ~80 generic actions (Combat/Travel/Economy/etc.). The big one.
|
||||
- **`IntelEngine`** — 15 actions (likely intelligence/observation-themed).
|
||||
- **`OStimNet_v0.9.2`** — ~10 actions, prefixed `tton_*` (sex/affection — adult mod).
|
||||
- **`SeverActions - SkyrimNet Action Pack`** - ~80 generic actions (Combat/Travel/Economy/etc.). The big one.
|
||||
- **`IntelEngine`** - 15 actions (likely intelligence/observation-themed).
|
||||
- **`OStimNet_v0.9.2`** - ~10 actions, prefixed `tton_*` (sex/affection - adult mod).
|
||||
|
||||
### 3. C++ registration
|
||||
|
||||
@@ -84,7 +84,7 @@ Actions can be grouped under categories defined in `cat_*.yaml` files. Categorie
|
||||
`Combat`, `Communication`, `Travel`, `Economy`, `Items`, `Magic`, `Outfit`, `Crafting`, `Scheduling`, `Command` (companion), `Arrest`.
|
||||
|
||||
**Two-stage flow:**
|
||||
1. GM (or native action selector) emits `ACTION: Communication PARAMS: {"intent": "express gratitude..."}` — picks the *category* and states the intent in natural language.
|
||||
1. GM (or native action selector) emits `ACTION: Communication PARAMS: {"intent": "express gratitude..."}` - picks the *category* and states the intent in natural language.
|
||||
2. C++ orchestrator fires a second LLM call using `prompts/native_action_selector_drilldown.prompt`, which sees only the leaf actions under that category plus the intent. Picks the specific leaf.
|
||||
|
||||
This pattern reduces cognitive load: instead of choosing among ~105 actions, the model picks one of ~10 categories then one of ~5-15 leaves.
|
||||
@@ -114,11 +114,11 @@ ACTION: None
|
||||
- Logged when not found: `[ActionManager.cpp:1783] ParseEmbeddedAction: No ACTION: line found in response`.
|
||||
- Logged when found: `[ActionManager.cpp:1814] ParseEmbeddedAction: Successfully parsed action 'X' with params: {...}`.
|
||||
|
||||
**Strip behavior:** `FilterActionLines` (`ActionManager.cpp:1840`) removes the recognized `ACTION:` line from dialogue text *before* TTS, so it isn't spoken aloud. **It only strips lines it parsed successfully** — malformed lines (the bug #2 case) stay in the text and get spoken.
|
||||
**Strip behavior:** `FilterActionLines` (`ActionManager.cpp:1840`) removes the recognized `ACTION:` line from dialogue text *before* TTS, so it isn't spoken aloud. **It only strips lines it parsed successfully** - malformed lines (the bug #2 case) stay in the text and get spoken.
|
||||
|
||||
---
|
||||
|
||||
## The two action selectors — how they relate
|
||||
## The two action selectors - how they relate
|
||||
|
||||
| Aspect | `gamemaster_action_selector` | `native_action_selector` |
|
||||
|---|---|---|
|
||||
@@ -148,9 +148,9 @@ We initially looked for them in `mods/SkyrimNet/` itself and didn't find them. T
|
||||
/home/dafit/Games/Skyrim/nimmersky/mods/OStimNet_v0.9.2/SKSE/Plugins/SkyrimNet/config/actions/*.yaml
|
||||
```
|
||||
|
||||
`[hypothesis]` — paths inferred from the contributing-mod names in the log; the agent didn't enumerate the actual files. Confirm by `find /home/dafit/Games/Skyrim/nimmersky/mods/ -name "*.yaml" -path "*/config/actions/*"`.
|
||||
`[hypothesis]` - paths inferred from the contributing-mod names in the log; the agent didn't enumerate the actual files. Confirm by `find /home/dafit/Games/Skyrim/nimmersky/mods/ -name "*.yaml" -path "*/config/actions/*"`.
|
||||
|
||||
This is also why "export from web UI" doesn't work cleanly — the UI sees actions in its registry but doesn't know they came from neighbor mods' folders, and can't reach back across mod boundaries to dump them.
|
||||
This is also why "export from web UI" doesn't work cleanly - the UI sees actions in its registry but doesn't know they came from neighbor mods' folders, and can't reach back across mod boundaries to dump them.
|
||||
|
||||
**Path forward for git-tracking:** symlink each contributing mod's `config/actions/` into a `nimmersky/skyrimnet/contributed-actions/{modname}/` archive, or write a one-shot collator script that copies them out.
|
||||
|
||||
@@ -158,13 +158,13 @@ This is also why "export from web UI" doesn't work cleanly — the UI sees actio
|
||||
|
||||
## The malformed-marker problem (bug #2)
|
||||
|
||||
The Dialogue model occasionally emits `OPENTRADE` (bare uppercase token) instead of `ACTION: OpenTrade`. The parser ignores the bare token, doesn't strip it, and it gets TTS'd as plain text — Arcadia speaks "OPENTRADE" out loud.
|
||||
The Dialogue model occasionally emits `OPENTRADE` (bare uppercase token) instead of `ACTION: OpenTrade`. The parser ignores the bare token, doesn't strip it, and it gets TTS'd as plain text - Arcadia speaks "OPENTRADE" out loud.
|
||||
|
||||
**Why it happens:** `submodules/user_final_instructions/0750_embedded_actions.prompt:6-9` lists actions to the LLM as bullets:
|
||||
```
|
||||
**Available Actions:**
|
||||
- `OPENTRADE` — Use ONLY if ...
|
||||
- `OFFERQUEST` — ...
|
||||
- `OPENTRADE` - Use ONLY if ...
|
||||
- `OFFERQUEST` - ...
|
||||
```
|
||||
The model sometimes emits the bare bullet name instead of the full `ACTION: OPENTRADE` line. The bullet list format encourages the mistake.
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@ Each agent is selected via a "variant" in `overwrite/.../config/OpenRouter.yaml`
|
||||
|
||||
| Variant | Model alias | Endpoint (this install) | `max_tokens` | Purpose |
|
||||
|---|---|---|---|---|
|
||||
| `gamemaster_evaluation` | `claude-sonnet-4-5-20250929` | `127.0.0.1:8000` (local Claude proxy) | **256** | "Should I act and how?" — fires GM action selector |
|
||||
| `gamemaster_evaluation` | `claude-sonnet-4-5-20250929` | `127.0.0.1:8000` (local Claude proxy) | **256** | "Should I act and how?" - fires GM action selector |
|
||||
| `AgentDefault` (Dialogue) | `eva` (custom local alias) | `10.0.30.21:31000` | 4096 | NPC dialogue generation |
|
||||
| `meta` | `omega` | `10.0.30.22:31004` | 100 | Mood eval, memory query gen, classifiers, target selection |
|
||||
| `vision` | `Qwen3-VL-8B-Instruct-abliterated-v2.Q4_K_M.gguf` | `10.0.30.22:31005` | 4000 | OmniSight scene description from screenshot |
|
||||
| `combat` / `action_evaluation` | `eva` | same as AgentDefault | 500 | Combat-flavor dialogue / native action selection |
|
||||
| `gamemaster_scene_planner` | (no dedicated variant captured — likely uses `AgentDefault`) | — | — | Pre-plans 4-6-beat scenes (consumed by `gamemaster_action_selector.prompt:96-119` via `scene_plan` context var) |
|
||||
| `intel_story_dm` | `claude-sonnet-4-5-20250929` | local proxy | — | IntelEngine plugin's persistent narrative DM |
|
||||
| `gamemaster_evaluation` (TTON) | `claude-sonnet-4-5-20250929` | local proxy | — | OstimNet plugin's nearby-NPC GM |
|
||||
| `gamemaster_scene_planner` | (no dedicated variant captured - likely uses `AgentDefault`) | - | - | Pre-plans 4-6-beat scenes (consumed by `gamemaster_action_selector.prompt:96-119` via `scene_plan` context var) |
|
||||
| `intel_story_dm` | `claude-sonnet-4-5-20250929` | local proxy | - | IntelEngine plugin's persistent narrative DM |
|
||||
| `gamemaster_evaluation` (TTON) | `claude-sonnet-4-5-20250929` | local proxy | - | OstimNet plugin's nearby-NPC GM |
|
||||
|
||||
`[note]` Models can be reconfigured per-agent by editing `OpenRouter.yaml`. The aliases (`eva`, `omega`) are user-defined and resolved by the OpenRouter routing layer.
|
||||
|
||||
@@ -26,25 +26,25 @@ Each agent is selected via a "variant" in `overwrite/.../config/OpenRouter.yaml`
|
||||
**When it fires** `[verified]` from trace dump:
|
||||
- Polling tick in continuous mode, roughly every `gamemaster.continuousSceneCooldownSeconds` (= 30s in this install). `[hypothesis]` exact timer source not isolated.
|
||||
- On player input arrival.
|
||||
- After a non-trivial in-game event (combat start, NPC death, location change) — though events are filterable via `Events.yaml`.
|
||||
- After a non-trivial in-game event (combat start, NPC death, location change) - though events are filterable via `Events.yaml`.
|
||||
|
||||
**Input context:**
|
||||
- Recent events (`gamemaster.recentEventsCount: 25` controls volume).
|
||||
- Nearby actors (`gamemaster.nearbyActorRadius: 600`).
|
||||
- Eligible actions list (populated dynamically from C++; in continuous mode `ACTION: None` is allowed only if exposed by the prompt — see `bugs-and-fixes.md` Bug #1).
|
||||
- Eligible actions list (populated dynamically from C++; in continuous mode `ACTION: None` is allowed only if exposed by the prompt - see `bugs-and-fixes.md` Bug #1).
|
||||
- Optional `scene_plan` if scene planner has run.
|
||||
|
||||
**Output:** exactly one line of the form
|
||||
```
|
||||
ACTION: ActionName PARAMS: {"key": "value", ...}
|
||||
```
|
||||
or `ACTION: None`. `max_tokens: 256` enforces this — no room for prose.
|
||||
or `ACTION: None`. `max_tokens: 256` enforces this - no room for prose.
|
||||
|
||||
**Consumers:** the C++ orchestrator parses the ACTION line and dispatches:
|
||||
- `StartConversation` / `ContinueConversation` → kicks the Dialogue pipeline for the named speaker/target with the given topic.
|
||||
- `Narrate` → triggers a narration-mode LLM call (no specific TTS speaker).
|
||||
- `None` → no-op, scene breathes.
|
||||
- Native actions (e.g. `OpenTrade`) when registered as eligible — fires the Papyrus/C++ callback.
|
||||
- Native actions (e.g. `OpenTrade`) when registered as eligible - fires the Papyrus/C++ callback.
|
||||
|
||||
---
|
||||
|
||||
@@ -74,14 +74,14 @@ or `ACTION: None`. `max_tokens: 256` enforces this — no room for prose.
|
||||
A family of small classifier/helper calls, all capped at `max_tokens: 100`.
|
||||
|
||||
**Prompts:**
|
||||
- `prompts/helpers/evaluate_mood.prompt` — post-dialogue mood update for the speaker
|
||||
- `prompts/helpers/generate_search_query.prompt` — turns a dialogue into a memory-retrieval query
|
||||
- `prompts/helpers/generate_profile.prompt` — generates/updates a character profile
|
||||
- `prompts/target_selectors/dialogue_speaker_selector.prompt` — picks who in a group should respond to player
|
||||
- `prompts/target_selectors/player_dialogue_target_selector.prompt` — picks the best NPC for player to address
|
||||
- `prompts/memory/generate_memory.prompt` and `memory_ranker.prompt` — memory creation/ranking
|
||||
- `prompts/transformers/native_dialogue_transformer.prompt` — text→text transformations
|
||||
- `prompts/transformers/universal_translator.prompt` — translation pipeline
|
||||
- `prompts/helpers/evaluate_mood.prompt` - post-dialogue mood update for the speaker
|
||||
- `prompts/helpers/generate_search_query.prompt` - turns a dialogue into a memory-retrieval query
|
||||
- `prompts/helpers/generate_profile.prompt` - generates/updates a character profile
|
||||
- `prompts/target_selectors/dialogue_speaker_selector.prompt` - picks who in a group should respond to player
|
||||
- `prompts/target_selectors/player_dialogue_target_selector.prompt` - picks the best NPC for player to address
|
||||
- `prompts/memory/generate_memory.prompt` and `memory_ranker.prompt` - memory creation/ranking
|
||||
- `prompts/transformers/native_dialogue_transformer.prompt` - text→text transformations
|
||||
- `prompts/transformers/universal_translator.prompt` - translation pipeline
|
||||
|
||||
**When they fire:** mostly post-dialogue. `target_selection_llm` runs *before* dialogue when player input arrives.
|
||||
|
||||
@@ -93,7 +93,7 @@ A family of small classifier/helper calls, all capped at `max_tokens: 100`.
|
||||
|
||||
**Prompts:** `prompts/native_action_selector.prompt` (stage 1: pick category) + `prompts/native_action_selector_drilldown.prompt` (stage 2: pick leaf action under that category).
|
||||
|
||||
**When it fires:** *after* the Dialogue agent produces text, asking "what in-game action does this dialogue imply?". `[hypothesis]` may not fire if `embed_actions_in_dialogue: true` and the Dialogue agent already emitted a valid `ACTION:` line — needs verification (see `open-questions.md`).
|
||||
**When it fires:** *after* the Dialogue agent produces text, asking "what in-game action does this dialogue imply?". `[hypothesis]` may not fire if `embed_actions_in_dialogue: true` and the Dialogue agent already emitted a valid `ACTION:` line - needs verification (see `open-questions.md`).
|
||||
|
||||
**Input:** the NPC's just-spoken line + the eligible action list with category groupings.
|
||||
|
||||
@@ -105,7 +105,7 @@ A family of small classifier/helper calls, all capped at `max_tokens: 100`.
|
||||
|
||||
---
|
||||
|
||||
## Vision Agent (`vision` variant — OmniSight)
|
||||
## Vision Agent (`vision` variant - OmniSight)
|
||||
|
||||
**Prompts:** `prompts/omnisight/describe_actor.prompt`, `describe_scene.prompt`, `describe_item.prompt`, `describe_location.prompt`, `describe_furniture.prompt`, with rendering-mode submodules in `submodules/omnisight_*/`.
|
||||
|
||||
@@ -113,7 +113,7 @@ A family of small classifier/helper calls, all capped at `max_tokens: 100`.
|
||||
|
||||
**Output:** scene description text (up to 4000 tokens), inserted into the Dialogue agent's context as the `omnisight` block.
|
||||
|
||||
**Consumers:** the Dialogue agent uses this to ground its response in what's visually present (objects, characters, environment) — not just what's in event logs.
|
||||
**Consumers:** the Dialogue agent uses this to ground its response in what's visually present (objects, characters, environment) - not just what's in event logs.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# SkyrimNet Architecture — High-Level Model
|
||||
# SkyrimNet Architecture - High-Level Model
|
||||
|
||||
## What SkyrimNet is
|
||||
|
||||
A multi-agent LLM orchestrator that hijacks vanilla Skyrim NPC behavior — replacing static dialogue topics and idle routines with context-aware, LLM-driven scenes. NPCs talk to each other and the player through generated dialogue; their world-affecting actions are picked from a registry of "actions" contributed by SkyrimNet itself and any cooperating mod.
|
||||
A multi-agent LLM orchestrator that hijacks vanilla Skyrim NPC behavior - replacing static dialogue topics and idle routines with context-aware, LLM-driven scenes. NPCs talk to each other and the player through generated dialogue; their world-affecting actions are picked from a registry of "actions" contributed by SkyrimNet itself and any cooperating mod.
|
||||
|
||||
`[verified]` from `SkyrimNet.log:14123-14266` (action library initialization), `Source/Scripts/SkyrimNetApi.psc` (public API), `prompts/gamemaster_action_selector.prompt` (GM orchestrator prompt).
|
||||
|
||||
@@ -63,20 +63,20 @@ SkyrimNet ships alongside a sibling SKSE plugin called **IntelEngine**. They're
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
`[verified]` All layers exist. The closed-source DLL is the only piece we cannot read directly — we infer behavior from logs, headers, Papyrus callbacks, and traces.
|
||||
`[verified]` All layers exist. The closed-source DLL is the only piece we cannot read directly - we infer behavior from logs, headers, Papyrus callbacks, and traces.
|
||||
|
||||
## The four agent families
|
||||
|
||||
Each agent maps to a "variant" in `OpenRouter.yaml`, which maps to a model/endpoint. See `agent-pipelines.md` for the full table.
|
||||
|
||||
1. **Gamemaster (GM)** — scene-level orchestrator. Decides "should anything happen now, and if so what?" Polls every ~30s in continuous mode + fires on player input. Emits one `ACTION:` line.
|
||||
2. **Dialogue** — generates the actual NPC speech. Triggered by GM actions like `StartConversation` / `ContinueConversation` or by player dialogue input. Can optionally append an `ACTION:` line for inline action firing.
|
||||
3. **Meta** — classifiers and helpers (mood eval, memory query generation, dialogue speaker selection). Capped at ~100 tokens per call.
|
||||
4. **Vision (OmniSight)** — describes the current scene from a screenshot. Uses a local Qwen3-VL model. Fires on `player_text_input` and `player_direct_input_voice` events.
|
||||
1. **Gamemaster (GM)** - scene-level orchestrator. Decides "should anything happen now, and if so what?" Polls every ~30s in continuous mode + fires on player input. Emits one `ACTION:` line.
|
||||
2. **Dialogue** - generates the actual NPC speech. Triggered by GM actions like `StartConversation` / `ContinueConversation` or by player dialogue input. Can optionally append an `ACTION:` line for inline action firing.
|
||||
3. **Meta** - classifiers and helpers (mood eval, memory query generation, dialogue speaker selection). Capped at ~100 tokens per call.
|
||||
4. **Vision (OmniSight)** - describes the current scene from a screenshot. Uses a local Qwen3-VL model. Fires on `player_text_input` and `player_direct_input_voice` events.
|
||||
|
||||
Plus a fifth implicit agent type:
|
||||
|
||||
5. **Native Action Selector** — *post-dialogue* classifier that asks "what in-game action does this NPC's spoken line imply?" Two-stage: category → leaf. Distinct from the GM's scene-level action selection.
|
||||
5. **Native Action Selector** - *post-dialogue* classifier that asks "what in-game action does this NPC's spoken line imply?" Two-stage: category → leaf. Distinct from the GM's scene-level action selection.
|
||||
|
||||
## End-to-end orchestration trace
|
||||
|
||||
@@ -130,7 +130,7 @@ if action == StartConversation or ContinueConversation:
|
||||
|
||||
`[hypothesis]` based on the trace structure and log volumes:
|
||||
|
||||
- **GM `max_tokens: 256`** is a hard ceiling. With three contributor mods registering ~105 actions total, the GM has to reason over a large `eligible_actions` list and emit one ACTION line — the two-stage drilldown and category wrapper exist precisely to compress this cognitive load.
|
||||
- **GM `max_tokens: 256`** is a hard ceiling. With three contributor mods registering ~105 actions total, the GM has to reason over a large `eligible_actions` list and emit one ACTION line - the two-stage drilldown and category wrapper exist precisely to compress this cognitive load.
|
||||
- **`wait_eligibility_results` blocks for up to 2500ms.** Slow Papyrus eligibility callbacks shrink the available action set. This is a Skyrim-VM-side performance dependency that no LLM tuning can fix.
|
||||
- **OmniSight vision** runs locally on a Qwen3-VL model. Image capture + inference adds latency before any text generation can begin.
|
||||
|
||||
@@ -138,8 +138,8 @@ if action == StartConversation or ContinueConversation:
|
||||
|
||||
- **whisper.cpp** for local STT (`SKSE/Plugins/SkyrimNet/libs/whisper.dll` + `ggml*.dll` for CPU/CUDA/Vulkan/OpenCL backends).
|
||||
- **all-MiniLM-L6-v2** sentence-transformer for semantic embedding of NPC memories (`SKSE/Plugins/SkyrimNet/models/all-MiniLM-L6-v2-tokenizer.json`).
|
||||
- **ONNX runtime** (`onnxruntime_skyrimnet.dll`) — likely VAD or auxiliary model inference.
|
||||
- **espeak-ng** voice data (`SKSE/Plugins/SkyrimNet/models/espeak-ng-data/`) — TTS phoneme tables for Piper/PocketTTS.
|
||||
- **ONNX runtime** (`onnxruntime_skyrimnet.dll`) - likely VAD or auxiliary model inference.
|
||||
- **espeak-ng** voice data (`SKSE/Plugins/SkyrimNet/models/espeak-ng-data/`) - TTS phoneme tables for Piper/PocketTTS.
|
||||
- **Spriggit** to git-track the .esp content as JSON.
|
||||
|
||||
## Cross-references
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Bugs and Fixes — Running Log
|
||||
# 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.
|
||||
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
|
||||
|
||||
@@ -9,24 +9,24 @@ Each entry has:
|
||||
- **Discovered:** absolute date
|
||||
- **Symptom:** what the user/we saw
|
||||
- **Root cause:** what's actually broken
|
||||
- **Fix:** what we changed (or "none — see notes")
|
||||
- **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]`
|
||||
## 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.
|
||||
**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.
|
||||
- `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.
|
||||
|
||||
@@ -36,7 +36,7 @@ Plus belt-and-braces: `tts.maxSegmentWords: 24 → 4` so even residual races los
|
||||
|
||||
---
|
||||
|
||||
## Bug #1 — Recursive farewell loop in continuous mode `[REVERTED — fix preserved, see notes]`
|
||||
## 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.
|
||||
@@ -53,32 +53,32 @@ Plus belt-and-braces: `tts.maxSegmentWords: 24 → 4` so even residual races los
|
||||
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.
|
||||
- **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.
|
||||
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.**"
|
||||
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.
|
||||
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.
|
||||
**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).
|
||||
**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]`
|
||||
## 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.
|
||||
@@ -100,40 +100,40 @@ OPENTRADE"
|
||||
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` — ...
|
||||
- `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.
|
||||
**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:**
|
||||
**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.
|
||||
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.
|
||||
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.
|
||||
**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]`
|
||||
## 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.
|
||||
**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.
|
||||
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
# Config Knobs — load-bearing settings
|
||||
# Config Knobs - load-bearing settings
|
||||
|
||||
YAML configs live in `overwrite/SKSE/Plugins/SkyrimNet/config/` (with active edits) and `mods/SkyrimNet/SKSE/Plugins/SkyrimNet/config/` (shipped baselines). 38 YAML files total, plus `defaults_manifest.json`.
|
||||
|
||||
This document focuses on the **load-bearing** settings — ones we've discovered have outsized effects on behavior. Full enumeration would be brittle and date-sensitive; the goal here is to capture the gotchas.
|
||||
This document focuses on the **load-bearing** settings - ones we've discovered have outsized effects on behavior. Full enumeration would be brittle and date-sensitive; the goal here is to capture the gotchas.
|
||||
|
||||
## SkyrimNet.yaml — the central config
|
||||
## SkyrimNet.yaml - the central config
|
||||
|
||||
`[verified]` Direct observations from this install + log/trace correlation.
|
||||
|
||||
| Setting | This install | What it controls | Gotcha |
|
||||
|---|---|---|---|
|
||||
| `dialogue.embedActionsInDialogue` | `true` | Lets the Dialogue model emit `ACTION:` lines inline after speech. | When `true`, the native_action_selector may be bypassed — and bug #2 (malformed markers) surfaces. When `false`, actions are strictly post-hoc via native selector. |
|
||||
| `dialogue.embedActionsInDialogue` | `true` | Lets the Dialogue model emit `ACTION:` lines inline after speech. | When `true`, the native_action_selector may be bypassed - and bug #2 (malformed markers) surfaces. When `false`, actions are strictly post-hoc via native selector. |
|
||||
| `dialogue.eligibilityCheckTimeoutMs` | `2500` | Bounds the `wait_eligibility_results` span. | Slow Papyrus eligibility callbacks (>2.5s) get dropped from the available action list for that turn. |
|
||||
| `dialogue.interruptDialogueOnVoiceStart` | `true` | Player STT triggers a barge-in on the NPC. | Distinct from TTS-side interrupts; affects whose voice cuts off whom. |
|
||||
| `dialogue.maxSegmentWords` (`tts.maxSegmentWords`) | `4` | TTS chunk size for streaming. | Was raised by us as part of the first-sentence-drop fix. Small chunks can amplify chunker race conditions; suspected innocent in NPC-NPC restatement. |
|
||||
| `gamemaster.enabled` | `true` | Master switch for the GM. | — |
|
||||
| `gamemaster.enabled` | `true` | Master switch for the GM. | - |
|
||||
| `gamemaster.agentEnabled` | `true` | `[unknown]` Distinct from `enabled`? Both default `true`; toggling untested. | See open-questions.md. |
|
||||
| `gamemaster.continuousSceneCooldownSeconds` | `30` | Minimum gap between GM polling ticks in continuous mode. | Lower = more LLM cost, more "alive" feel. Higher = cheaper, quieter scenes. |
|
||||
| `gamemaster.actionCooldownSeconds` | `180` | Per-NPC action cooldown. | Prevents the same NPC from being action-targeted repeatedly. |
|
||||
| `gamemaster.nearbyActorRadius` | `600` | Radius around player the GM considers. | Skyrim units (~10cm each, so ~60m). |
|
||||
| `gamemaster.recentEventsCount` | `25` | How much event history goes into the GM prompt. | Higher = more context, more tokens, more cost. |
|
||||
| `gamemaster.requestTimeoutSeconds` | `30` | Per-call LLM timeout for the GM. | — |
|
||||
| `gamemaster.continuousSceneCooldownSeconds` | `30` | (duplicate row — see above) | — |
|
||||
| `gamemaster.requestTimeoutSeconds` | `30` | Per-call LLM timeout for the GM. | - |
|
||||
| `gamemaster.continuousSceneCooldownSeconds` | `30` | (duplicate row - see above) | - |
|
||||
| `interaction.maxDistance` | `250` | Default interaction distance. | Affects who hears what. |
|
||||
| `interaction.normalMaxDistance` | `250` | Normal speech audibility. | — |
|
||||
| `interaction.whisperMaxDistance` | `100` | Whisper audibility. | — |
|
||||
| `interaction.normalMaxDistance` | `250` | Normal speech audibility. | - |
|
||||
| `interaction.whisperMaxDistance` | `100` | Whisper audibility. | - |
|
||||
| `events.preloadCount` | `1000` | Events warmed at startup. | Affects load time + initial memory query corpus. |
|
||||
| `narration.enabled` | `false` | Suppresses asterisk-style narration globally. | Referenced by `is_narration_enabled()` decorator in many prompts; toggling changes prompt content materially. |
|
||||
| `subtitles.enableNPCSubtitles` | `true` | Whether NPC subtitles render. | — |
|
||||
| `dbvo.enabled` | `true` | Dialogue Background Voice Over — routes TTS through vanilla Skyrim dialogue topics. | The "first-sentence-dropped" bug we hit was traced to this. Disabling skips lip-sync and topic-coupling. |
|
||||
| `subtitles.enableNPCSubtitles` | `true` | Whether NPC subtitles render. | - |
|
||||
| `dbvo.enabled` | `true` | Dialogue Background Voice Over - routes TTS through vanilla Skyrim dialogue topics. | The "first-sentence-dropped" bug we hit was traced to this. Disabling skips lip-sync and topic-coupling. |
|
||||
| `shouldSilenceActors` | `true` | Silences actors before TTS plays (prevents vanilla audio overlap). | Can race the first audio chunk and silence it. |
|
||||
|
||||
`[verified]` `enableNPCNodeUpdates: true` and `dbvo.fixSubtitleDuringTTS: true` were the toggles that fixed the user's "first sentence not played" issue (see `bugs-and-fixes.md` historical context).
|
||||
@@ -36,8 +36,8 @@ This document focuses on the **load-bearing** settings — ones we've discovered
|
||||
## Agents.yaml
|
||||
|
||||
Defines per-agent variant assignments and behavior. Each agent has:
|
||||
- `variant` — which OpenRouter variant to use (cross-references `OpenRouter.yaml`)
|
||||
- `enabled` — master toggle for the agent
|
||||
- `variant` - which OpenRouter variant to use (cross-references `OpenRouter.yaml`)
|
||||
- `enabled` - master toggle for the agent
|
||||
- Possibly `cooldown`, `timeout`, model-specific overrides
|
||||
|
||||
`[hypothesis]` Editing `Agents.yaml` is how a user changes which model handles which agent (e.g., "use Claude for dialogue too, not just GM"). Confirm by inspection.
|
||||
@@ -47,23 +47,23 @@ Defines per-agent variant assignments and behavior. Each agent has:
|
||||
Variant → model + endpoint + per-call defaults. See `agent-pipelines.md` for the full table extracted from this install.
|
||||
|
||||
Key sections:
|
||||
- `default_params` per variant — `max_tokens`, `temperature`, `top_p`, etc.
|
||||
- `endpoint` — the URL the OpenRouter routing layer sends to. In this install, mostly local IPs (`10.0.30.x`) plus a Claude proxy on `127.0.0.1:8000`.
|
||||
- `model` — the OpenRouter alias or local model ID.
|
||||
- `default_params` per variant - `max_tokens`, `temperature`, `top_p`, etc.
|
||||
- `endpoint` - the URL the OpenRouter routing layer sends to. In this install, mostly local IPs (`10.0.30.x`) plus a Claude proxy on `127.0.0.1:8000`.
|
||||
- `model` - the OpenRouter alias or local model ID.
|
||||
|
||||
## Actions.yaml
|
||||
|
||||
`[hypothesis]` Defines global action-system policies: which categories are enabled, default priorities, eligibility-check defaults. Doesn't define individual actions — those come from `config/actions/*.yaml` in *contributing mods'* folders (see `action-system.md`).
|
||||
`[hypothesis]` Defines global action-system policies: which categories are enabled, default priorities, eligibility-check defaults. Doesn't define individual actions - those come from `config/actions/*.yaml` in *contributing mods'* folders (see `action-system.md`).
|
||||
|
||||
## Events.yaml
|
||||
|
||||
~40 event types, each with toggles:
|
||||
- `enabled` — does SkyrimNet listen for this event at all
|
||||
- `persistent` — does the event get stored in the long-term log
|
||||
- `shortLivedEnabled` — short-lived event variant
|
||||
- `allowNPCReaction` — can NPCs react to this event via the GM
|
||||
- `npcReactionCooldown` — per-NPC cooldown for reacting
|
||||
- `interrupt` — does this event interrupt ongoing dialogue
|
||||
- `enabled` - does SkyrimNet listen for this event at all
|
||||
- `persistent` - does the event get stored in the long-term log
|
||||
- `shortLivedEnabled` - short-lived event variant
|
||||
- `allowNPCReaction` - can NPCs react to this event via the GM
|
||||
- `npcReactionCooldown` - per-NPC cooldown for reacting
|
||||
- `interrupt` - does this event interrupt ongoing dialogue
|
||||
|
||||
**Currently disabled in user's config** `[verified]` from the user's install:
|
||||
- `quest_stage`
|
||||
@@ -85,7 +85,7 @@ Controls the vector memory system (semantic embeddings via all-MiniLM-L6-v2):
|
||||
|
||||
## OmniSight.yaml
|
||||
|
||||
Vision-agent settings — when to capture screenshots, how often, scene description verbosity.
|
||||
Vision-agent settings - when to capture screenshots, how often, scene description verbosity.
|
||||
|
||||
## MCP.yaml
|
||||
|
||||
@@ -99,8 +99,8 @@ One YAML per supported TTS backend:
|
||||
Active backend selected by `tts.engine` in `SkyrimNet.yaml`. This install uses `pocket_tts`.
|
||||
|
||||
Streaming params worth knowing (in `PocketTTS.yaml`):
|
||||
- `streaming.max_words_to_process: 32` — max chunk size
|
||||
- `streaming.min_words_to_process_initial: 16` — first-chunk minimum (was a suspect for the first-sentence-drop bug)
|
||||
- `streaming.max_words_to_process: 32` - max chunk size
|
||||
- `streaming.min_words_to_process_initial: 16` - first-chunk minimum (was a suspect for the first-sentence-drop bug)
|
||||
- `streaming.process_on_sentence_boundary: true`
|
||||
- `streaming.process_on_word_count: true`
|
||||
- `streaming.max_buffers_per_actor: 4`
|
||||
@@ -111,7 +111,7 @@ Speech-to-text settings (whisper.cpp local). VAD thresholds, language, sample ra
|
||||
|
||||
## defaults_manifest.json
|
||||
|
||||
`[hypothesis]` JSON dump of every config key with its default value, structured by section name. Looks like the **schema the DLL seeds first-run config from** — when a YAML doesn't exist, the DLL writes one populated from this manifest.
|
||||
`[hypothesis]` JSON dump of every config key with its default value, structured by section name. Looks like the **schema the DLL seeds first-run config from** - when a YAML doesn't exist, the DLL writes one populated from this manifest.
|
||||
|
||||
**Sections present** (28 total): `ActorFilter`, `Agents`, `BardSinging`, `ChatUI`, `Chatterbox`, `DialogueFilter`, `Diary`, `DynamicBio`, `ElevenLabs`, `Entity`, `Events`, `Hotkey`, `Inworld`, `Memory`, `MemoryFilter`, `OmniSight`, `OpenRouter`, `Piper`, `PlayerDialogue`, `PocketTTS`, `STT`, `UniversalTranslator`, `VastAI`, `VirtualEntities`, `VoiceSamples`, `WebServer`, `XTTS`, `Zonos`.
|
||||
|
||||
@@ -119,11 +119,11 @@ Each section name matches a YAML file. Verify role by deleting a YAML and watchi
|
||||
|
||||
## Plugins subtree
|
||||
|
||||
`config/plugins/IntelEngine/` — the IntelEngine sibling plugin keeps its config under SkyrimNet's config root. Notable files: `factions.yaml`, `settings.yaml`. `[unknown]` exact contents.
|
||||
`config/plugins/IntelEngine/` - the IntelEngine sibling plugin keeps its config under SkyrimNet's config root. Notable files: `factions.yaml`, `settings.yaml`. `[unknown]` exact contents.
|
||||
|
||||
## Editing safety
|
||||
|
||||
- **Always edit `overwrite/.../config/`**, never `mods/.../config/` directly. The overwrite layer is what the game reads.
|
||||
- **Most settings hot-reload.** Restart only needed for backend swaps (TTS engine, STT) or fundamental architecture changes.
|
||||
- **Backup before changes:** `cp foo.yaml foo.yaml.backup` is the pattern we've used.
|
||||
- **The in-game MCM (Mod Configuration Menu)** writes back to these YAMLs — UI tweaks persist as YAML edits, which is convenient but means the UI is yet another writer to be aware of.
|
||||
- **The in-game MCM (Mod Configuration Menu)** writes back to these YAMLs - UI tweaks persist as YAML edits, which is convenient but means the UI is yet another writer to be aware of.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Open Questions
|
||||
|
||||
Things we believe but couldn't fully verify in the first discovery pass, or that we observed but couldn't explain. Each question has a **verification path** — what would resolve it.
|
||||
Things we believe but couldn't fully verify in the first discovery pass, or that we observed but couldn't explain. Each question has a **verification path** - what would resolve it.
|
||||
|
||||
When a question gets resolved, move the answer into the relevant detail file (`architecture.md`, `agent-pipelines.md`, etc.) and delete the entry here. Or move it to `bugs-and-fixes.md` if it turned out to be a bug.
|
||||
|
||||
@@ -22,11 +22,11 @@ When a question gets resolved, move the answer into the relevant detail file (`a
|
||||
|
||||
## Q2: What's the actual GM polling timer source?
|
||||
|
||||
**Why we care:** We've been calling it "polling every ~30s" based on `gamemaster.continuousSceneCooldownSeconds`, but the trace doesn't show a clean periodic tick — fires correlate with events too.
|
||||
**Why we care:** We've been calling it "polling every ~30s" based on `gamemaster.continuousSceneCooldownSeconds`, but the trace doesn't show a clean periodic tick - fires correlate with events too.
|
||||
|
||||
**What we observed:** GM `gamemaster_evaluation` calls fire at irregular intervals in the captured logs. Sometimes ~30s apart, sometimes seconds after a player input or NPC event.
|
||||
|
||||
**Hypothesis:** The "polling" is actually event-driven — every relevant Skyrim event (per `Events.yaml` filters) triggers a GM evaluation, with `continuousSceneCooldownSeconds` as a *minimum* gap to prevent spam. So idle silence + cooldown = ~30s tick; active scene = much faster.
|
||||
**Hypothesis:** The "polling" is actually event-driven - every relevant Skyrim event (per `Events.yaml` filters) triggers a GM evaluation, with `continuousSceneCooldownSeconds` as a *minimum* gap to prevent spam. So idle silence + cooldown = ~30s tick; active scene = much faster.
|
||||
|
||||
**Verification path:** Disable all event-triggered firings in `Events.yaml` (set `allowNPCReaction: false` everywhere), enable continuous mode, sit in an empty cell with no NPCs, watch trace timing. Should see clean 30s ticks if cooldown is the only timer.
|
||||
|
||||
@@ -52,7 +52,7 @@ When a question gets resolved, move the answer into the relevant detail file (`a
|
||||
|
||||
## Q5: Does `gamemaster_scene_planner` ever fire in current config?
|
||||
|
||||
**Why we care:** `prompts/gamemaster_action_selector.prompt:96-119` references a `scene_plan` context variable, suggesting a separate planner agent populates it. But the captured trace dump shows no `scene_planner` spans — either it never fires, or it fires under a different name, or it's gated behind an MCM toggle that's currently off.
|
||||
**Why we care:** `prompts/gamemaster_action_selector.prompt:96-119` references a `scene_plan` context variable, suggesting a separate planner agent populates it. But the captured trace dump shows no `scene_planner` spans - either it never fires, or it fires under a different name, or it's gated behind an MCM toggle that's currently off.
|
||||
|
||||
**Verification path:** Search `Agents.yaml` for any scene_planner-related variant. Check MCM for a "Scene Planner" toggle. Enable if found, observe traces.
|
||||
|
||||
@@ -81,7 +81,7 @@ When a question gets resolved, move the answer into the relevant detail file (`a
|
||||
|
||||
**Why we care:** It's a sibling SKSE plugin that registers 15 actions and has its own SQLite DB. We don't know its scope.
|
||||
|
||||
**What we have:** Plugin name suggests "intelligence" — possibly classification, world observation, scene tagging, or persistent narrative state.
|
||||
**What we have:** Plugin name suggests "intelligence" - possibly classification, world observation, scene tagging, or persistent narrative state.
|
||||
|
||||
**Verification path:** Check if IntelEngine ships its own README in the mod folder. Read its config files at `overwrite/.../config/plugins/IntelEngine/` (`factions.yaml`, `settings.yaml`). Inspect its action YAMLs to see what behaviors it registers.
|
||||
|
||||
@@ -91,7 +91,7 @@ When a question gets resolved, move the answer into the relevant detail file (`a
|
||||
|
||||
**Why we care:** The `0014_regenerate_embeddings` migration confirms an embedding pipeline. We know `all-MiniLM-L6-v2` is the embedding model. We don't know retrieval-time semantics: similarity threshold, top-K, recency weighting, faction filtering.
|
||||
|
||||
**Verification path:** Read `Memory.yaml` for tunables. Trace a `memory_search_query_generation` span end-to-end — the meta agent generates a query string, then the DLL must embed it, run a vector similarity search against the `vector_memory_system` table, and inject results into the dialogue context. Check the `SkyrimNet.log` for SQL query patterns.
|
||||
**Verification path:** Read `Memory.yaml` for tunables. Trace a `memory_search_query_generation` span end-to-end - the meta agent generates a query string, then the DLL must embed it, run a vector similarity search against the `vector_memory_system` table, and inject results into the dialogue context. Check the `SkyrimNet.log` for SQL query patterns.
|
||||
|
||||
---
|
||||
|
||||
@@ -99,7 +99,7 @@ When a question gets resolved, move the answer into the relevant detail file (`a
|
||||
|
||||
**Why we care:** dafit already authors `nimmerverse/nimmersky/oghma-sknpack/` knowledge packs (Whiterun, Winterhold, Solstheim, etc.). Understanding the runtime ingestion + storage format would let us validate that our packs are well-formed.
|
||||
|
||||
**Verification path:** Read `0017_knowledge_packs.sql` to see the table schema. Inspect an oghma-sknpack file to see the source format. Trace ingestion in `SkyrimNet.log` — search for "knowledge_pack" or "sknpack" patterns. The `oghma-proxy` tool probably has documentation we wrote ourselves.
|
||||
**Verification path:** Read `0017_knowledge_packs.sql` to see the table schema. Inspect an oghma-sknpack file to see the source format. Trace ingestion in `SkyrimNet.log` - search for "knowledge_pack" or "sknpack" patterns. The `oghma-proxy` tool probably has documentation we wrote ourselves.
|
||||
|
||||
---
|
||||
|
||||
@@ -113,7 +113,7 @@ When a question gets resolved, move the answer into the relevant detail file (`a
|
||||
|
||||
## Q12: How does the in-game prompt editor's `propose_prompt_edit` tool flow back into the override file?
|
||||
|
||||
**Why we care:** The runtime override at `overwrite/prompts/agent_chat.prompt.backup.1776372078` proves edits via the in-game LLM-driven prompt editor (`agent_prompt_helper.prompt`) get persisted with epoch-stamped backups. We don't know the exact write semantics — does it always backup-and-replace, or only on accept?
|
||||
**Why we care:** The runtime override at `overwrite/prompts/agent_chat.prompt.backup.1776372078` proves edits via the in-game LLM-driven prompt editor (`agent_prompt_helper.prompt`) get persisted with epoch-stamped backups. We don't know the exact write semantics - does it always backup-and-replace, or only on accept?
|
||||
|
||||
**Verification path:** Use the in-game prompt editor on a low-stakes prompt, watch what files appear/change in `overwrite/prompts/`. Document the backup naming and write-on-accept behavior.
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ Inja is a C++ Jinja2-inspired template engine. It looks similar but is **strictl
|
||||
### Key differences from Jinja2 worth knowing
|
||||
|
||||
- **Limited filter set.** Standard Jinja filters like `default`, `upper`, `length`, `join`, `sort` are present; less common ones may not be.
|
||||
- **`{% set %}` only affects render context** — does NOT mutate input data.
|
||||
- **Array manipulation functions return new arrays** — `append`, `extend`, etc. don't modify originals.
|
||||
- **`{% set %}` only affects render context** - does NOT mutate input data.
|
||||
- **Array manipulation functions return new arrays** - `append`, `extend`, etc. don't modify originals.
|
||||
- **Template inheritance is limited:** `{% extends "..." %}` + `{% block name %}...{% endblock %}` works; multiple inheritance does not.
|
||||
- **No macros** in the Jinja2 sense.
|
||||
- **Backslashed paths in includes:** `render_template("components\\event_history_compact")` — the `\\` is the path separator in include calls.
|
||||
- **Backslashed paths in includes:** `render_template("components\\event_history_compact")` - the `\\` is the path separator in include calls.
|
||||
|
||||
### SkyrimNet-specific Inja extensions
|
||||
|
||||
@@ -33,19 +33,19 @@ Inja is a C++ Jinja2-inspired template engine. It looks similar but is **strictl
|
||||
```
|
||||
|
||||
**Special blocks:**
|
||||
- `[ raw ] ... [ end raw ]` — prevents Inja parsing of `{{...}}` etc. Used for embedded examples that contain template syntax literally.
|
||||
- `[ cache ] ... [ end cache ]` — marks a block as cacheable for prompt caching across requests.
|
||||
- `[ raw ] ... [ end raw ]` - prevents Inja parsing of `{{...}}` etc. Used for embedded examples that contain template syntax literally.
|
||||
- `[ cache ] ... [ end cache ]` - marks a block as cacheable for prompt caching across requests.
|
||||
|
||||
**Include functions:**
|
||||
- `render_template("path\\with\\backslashes")` — include another `.prompt` from `prompts/`.
|
||||
- `render_subcomponent("subdir", render_mode)` — render numbered submodules from a subdir in load order.
|
||||
- `render_character_profile("mode", uuid)` — render an NPC's bio with a render mode (`full`, `target`, `transform`, `thoughts`, `short_inline`, `interject_inline`).
|
||||
- `render_template("path\\with\\backslashes")` - include another `.prompt` from `prompts/`.
|
||||
- `render_subcomponent("subdir", render_mode)` - render numbered submodules from a subdir in load order.
|
||||
- `render_character_profile("mode", uuid)` - render an NPC's bio with a render mode (`full`, `target`, `transform`, `thoughts`, `short_inline`, `interject_inline`).
|
||||
|
||||
**Decorators** (DLL-registered functions callable from templates):
|
||||
- `decnpc(uuid)` — universal NPC info accessor; returns object with `name`, `firstName`, `lastName`, `race`, `gender`, pronouns (`subjectivePronoun`, `objectivePronoun`, `possessivePronoun`, `reflexivePronoun`), `level`, `health`, `magicka`, `stamina`, `faction[]`, `isInCombat`, `isHostile`, `isDead`, `isBusy`, all skill values.
|
||||
- `decnpc(uuid)` - universal NPC info accessor; returns object with `name`, `firstName`, `lastName`, `race`, `gender`, pronouns (`subjectivePronoun`, `objectivePronoun`, `possessivePronoun`, `reflexivePronoun`), `level`, `health`, `magicka`, `stamina`, `faction[]`, `isInCombat`, `isHostile`, `isDead`, `isBusy`, all skill values.
|
||||
- 30+ other decorators observed in trace dump: `is_in_faction`, `is_player`, `get_arousal_state`, `format_event`, `short_time`, `get_name`, `get_nearby_npc_list`, `get_recent_events`, `get_quest_stage`, `is_narration_enabled`, `get_relevant_memories`, `get_scene_context`, `get_world_knowledge`, `papyrus_util`, `outfit_context`, etc.
|
||||
|
||||
`[unknown]` Full decorator list with signatures — dynamically generated by the DLL via `prompts/documentation/main.prompt`. Would require dumping the in-game web UI's `/api/documentation` endpoint to enumerate.
|
||||
`[unknown]` Full decorator list with signatures - dynamically generated by the DLL via `prompts/documentation/main.prompt`. Would require dumping the in-game web UI's `/api/documentation` endpoint to enumerate.
|
||||
|
||||
---
|
||||
|
||||
@@ -59,7 +59,7 @@ Load priority **high → low** (later layers shadow earlier ones for the same pa
|
||||
| 2 | `mods/SkyrimNet/SKSE/Plugins/SkyrimNet/prompts/` | **Shipped baseline + dev/user edits.** | Where our `gamemaster_action_selector.prompt` fix lives. The `.backup` from the fix is here too: `gamemaster_action_selector.prompt.backup`. |
|
||||
| 3 | `mods/SkyrimNet/SKSE/Plugins/SkyrimNet/original_prompts/` | **Pristine upstream baseline.** Useful for diffing to detect upstream changes. | `[hypothesis]` Only `characters/` and `submodules/system_head/` are populated; the rest of upstream prompts may be assumed copied to `prompts/` at install time, or this directory only holds pristine *overrideable* defaults. |
|
||||
|
||||
**Practical implication:** before editing any prompt, check if a runtime override exists in `overwrite/prompts/`. If it does, *that* is the active version — editing in `mods/prompts/` will be silently shadowed.
|
||||
**Practical implication:** before editing any prompt, check if a runtime override exists in `overwrite/prompts/`. If it does, *that* is the active version - editing in `mods/prompts/` will be silently shadowed.
|
||||
|
||||
`[verified]` overlay pattern is the same shape as Linux `/etc/foo.conf` + `/etc/foo.conf.d/` + package defaults.
|
||||
|
||||
@@ -76,7 +76,7 @@ Submodules in `prompts/submodules/<category>/` are loaded in numerical filename
|
||||
| `0500_…0799_` | Guidelines and behavioral rules | `0750_embedded_actions.prompt` (the ACTION: format spec) |
|
||||
| `0800_…0999_` | Late instructions and special toggles | direct narration, recent state changes |
|
||||
| `7000_…7999_` | Memories and progression | NPC memory blocks |
|
||||
| `9990_` | Speech style — rendered last to override earlier voice instructions | speech style submodules |
|
||||
| `9990_` | Speech style - rendered last to override earlier voice instructions | speech style submodules |
|
||||
|
||||
Earlier numbers establish baseline; higher numbers customize/override. Fits the late-binding-wins pattern.
|
||||
|
||||
@@ -107,10 +107,10 @@ Earlier numbers establish baseline; higher numbers customize/override. Fits the
|
||||
|
||||
---
|
||||
|
||||
## Editing prompts safely — checklist
|
||||
## Editing prompts safely - checklist
|
||||
|
||||
1. **Is there an override in `overwrite/prompts/`?** If yes, that's the active version. Edit it (the in-game UI does this) or delete it to fall back to `mods/prompts/`.
|
||||
2. **Diff against `original_prompts/`** to see what the upstream baseline says.
|
||||
3. **Make a `.backup` next to the file** before significant edits — SkyrimNet itself does this convention with epoch-stamped backups; we use the simpler `.backup` suffix.
|
||||
4. **Hot-reload picks up changes** — no need to restart Skyrim for prompt edits, but model behavior may take effect on the next agent firing.
|
||||
5. **Test against logs** — watch `openrouter_input.log` to see what prompt text the model actually receives after your edit.
|
||||
3. **Make a `.backup` next to the file** before significant edits - SkyrimNet itself does this convention with epoch-stamped backups; we use the simpler `.backup` suffix.
|
||||
4. **Hot-reload picks up changes** - no need to restart Skyrim for prompt edits, but model behavior may take effect on the next agent firing.
|
||||
5. **Test against logs** - watch `openrouter_input.log` to see what prompt text the model actually receives after your edit.
|
||||
|
||||
Reference in New Issue
Block a user