complete arch. discovery - added .gitignore of main - source

This commit is contained in:
2026-04-19 04:30:52 +02:00
parent 6fb10339a1
commit 5eda2e7ce6
9 changed files with 1054 additions and 0 deletions

173
skyrimnet/action-system.md Normal file
View File

@@ -0,0 +1,173 @@
# Action System
This is the most complex subsystem and the one we've already broken once. Read carefully before editing anything that touches actions.
## The three registration paths
`[verified]` from `Source/Scripts/SkyrimNetApi.psc:27-31` (Papyrus path), `SkyrimNet.log:14123-14266` (CustomActionManager loader), `CppAPI/PublicAPI.h:93-97` (C++ path), and on-disk YAML inspection.
### 1. Papyrus registration
A mod's Papyrus script calls:
```papyrus
SkyrimNetApi.RegisterAction(
string actionName, ; UPPERCASED in the registry — "OpenTrade" → "OPENTRADE"
string actionDescription,
string eligibilityScriptName,
string eligibilityFunctionName,
string executionScriptName,
string executionFunctionName,
string triggeringEventTypesCsv,
string actionType, ; "PAPYRUS" or "PAPYRUS_CUSTOM"
int priority,
string parameterSchemaJson,
string customCategory,
string tagsCsv
)
```
The C++ DLL keeps a registry of these. When the LLM emits `ACTION: OPENTRADE PARAMS: {...}`, the DLL:
1. Looks up `OPENTRADE` in the registry.
2. Calls the eligibility function via Papyrus (`Action_IsEligible` style), waits up to `dialogue.eligibilityCheckTimeoutMs` (= 2500ms).
3. If eligible, calls the execution function with the parameters.
**Shipped Papyrus actions** (from `Source/Scripts/skynet_Library.psc:130-315`):
`OpenTrade`, `AccompanyTarget`, `StopAccompanying`, `WaitHere`, `Gesture` (with anim enum), `RentRoom`, `CompanionFollow`, `CompanionWait`, `CompanionInventory`, `CompanionGiveTask`.
`[verified]` `SkyrimNet.log:16393` shows: `ActionLibrary::RegisterPapyrusAction for action: OPENTRADE`.
### 2. YAML registration
A mod can drop YAML files into `SKSE/Plugins/SkyrimNet/config/actions/*.yaml` *in its own mod folder*. SkyrimNet's `CustomActionManager::Initialize` scans across all installed mods at load.
**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`:
```yaml
customCategory: Combat
name: AttackTarget
description: ...
questEditorId: ... # the Skyrim Quest holding the function
scriptName: ... # Papyrus script attached to the quest
executionFunctionName: ...
parameterMapping:
- parameterName: target
decorator: get_nearby_actor # or other decorator → Skyrim object
enabled: true
defaultPriority: 50
eligibilityRules:
- decorator: is_in_combat
operator: equals
value: true
# AND/OR combinations supported
```
**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).
### 3. C++ registration
External SKSE plugins can call `PublicRegisterCPPAction()` from `CppAPI/PublicAPI.h:93-97` to register C++-implemented actions directly. Avoids the Papyrus VM round-trip; useful for performance-critical or low-level actions.
`[unknown]` whether any currently-loaded mod uses this path in this install.
---
## Action categories (the wrapper layer)
Actions can be grouped under categories defined in `cat_*.yaml` files. Categories let the LLM "intent-pick" without seeing all leaf actions.
**Known categories** `[verified]` from logs:
`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.
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.
---
## The `ACTION:` parser grammar
`[verified]` from log behavior at `ActionManager.cpp:1783` and `:1814`.
**Required form (start of a line):**
```
ACTION: ActionName
ACTION: ActionName PARAMS: {"param": "value", ...}
ACTION: None
```
**Strict requirements:**
- The literal **`ACTION: `** prefix (with the colon and space) at the start of a line.
- Action name must match a registered action **exactly** (case-sensitive, except Papyrus actions are uppercase in the registry).
- `PARAMS:` if present must be valid JSON.
- The action line must be on its own line, after any dialogue text (with `embed_actions_in_dialogue: true`).
**No leniency:**
- Bare action names (e.g. `OPENTRADE` alone) are NOT recognized. They fall through to dialogue text.
- No fuzzy matching. No alternate prefixes (`Action:`, `ACT:`, `[ACTION]`).
- 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.
---
## The two action selectors — how they relate
| Aspect | `gamemaster_action_selector` | `native_action_selector` |
|---|---|---|
| **Scope** | scene-level orchestration | per-NPC, post-dialogue attribution |
| **When** | GM polling tick, player input | after Dialogue agent produces text |
| **Picks from** | StartConversation / ContinueConversation / Narrate / None / native actions | the eligible-actions registry, two-stage |
| **Variant** | `gamemaster_evaluation` | `action_evaluation` |
| **Model** | claude-sonnet (this install) | eva (this install) |
| **`max_tokens`** | 256 | 500 |
| **Output** | one ACTION line | one ACTION line (after drilldown: two LLM calls total) |
They are **complementary, not competitive**:
- The GM directs *who speaks and what about*.
- The native action selector classifies *what physically/mechanically happens* once an NPC has spoken.
**With `embed_actions_in_dialogue: true`:** the Dialogue agent itself can emit an `ACTION:` line inline, which **bypasses** the native action selector entirely. Faster, but more prone to format errors → bug #2 territory.
---
## Where custom actions actually live (resolved)
We initially looked for them in `mods/SkyrimNet/` itself and didn't find them. They live in **other mods' folders**:
```
/home/dafit/Games/Skyrim/nimmersky/mods/SeverActions - SkyrimNet Action Pack/SKSE/Plugins/SkyrimNet/config/actions/*.yaml
/home/dafit/Games/Skyrim/nimmersky/mods/IntelEngine/SKSE/Plugins/SkyrimNet/config/actions/*.yaml
/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/*"`.
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.
---
## 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.
**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` — ...
```
The model sometimes emits the bare bullet name instead of the full `ACTION: OPENTRADE` line. The bullet list format encourages the mistake.
**Why it became visible only after fixing bug #1:** the over-firing GM loop gave the Dialogue model 3-5 retries per scene; bug #2 was statistically masked. Fixing the loop removed the retries, exposing bug #2.
See [`bugs-and-fixes.md`](bugs-and-fixes.md) for fix candidates.