docs: add invention system refinements design
Covers duplicate fix (load bug), name normalization, stat-driven invention selection, race condition guard, and token usage tracking. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# Invention System Refinements — Design
|
||||
|
||||
## Problem
|
||||
|
||||
The invention system has several issues:
|
||||
1. **Duplicate inventions**: After server restart, invented items are loaded into the timeline but not re-registered into `itemRegistry`/`recipeRegistry`. The duplicate check runs against an empty registry, so previously invented items can be invented again.
|
||||
2. **Name inconsistency**: The LLM sometimes returns snake_case names ("wooden_hammer") instead of display names ("Wooden Hammer").
|
||||
3. **No personality influence on invention choice**: Only INT and CUR affect invention *probability*, but no stats influence *what* gets invented. This limits diversity across worlds.
|
||||
4. **No token usage visibility**: No way to track LLM API costs for future optimization.
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. Fix: Re-register inventions on load
|
||||
|
||||
In `GameLoop.ts`, after loading inventions from the DB into the timeline, iterate through each `InventionEntry` and register it into `itemRegistry` and `recipeRegistry`. This restores the duplicate-prevention invariant across restarts.
|
||||
|
||||
### 2. Fix: Name normalization
|
||||
|
||||
Add "Always use Title Case for item names" to the LLM prompt. The `toSnakeCase()` ID derivation in `inventionValidator.ts` stays as-is — it correctly generates IDs from either format.
|
||||
|
||||
### 3. Feature: Stat-driven invention selection
|
||||
|
||||
Change the LLM prompt from "invent ONE item" to "consider 3 possible inventions, then select the one that best matches this NPC's personality." Include the full 10-stat block with readable names and scale context. The LLM returns a single JSON object (same schema as today) plus a short `reasoning` field. No extra calls, no inflated response.
|
||||
|
||||
#### Revised prompt
|
||||
|
||||
**System:**
|
||||
```
|
||||
You are an inventor in a medieval fantasy village simulation. Given available materials and an NPC's stats, consider 3 possible inventions, then select the one that best fits the NPC's personality. Respond ONLY with valid JSON. No markdown.
|
||||
```
|
||||
|
||||
**User:**
|
||||
```
|
||||
{{npcName}} is having a creative moment.
|
||||
|
||||
Stats (each ranges 3-18, 10 is average):
|
||||
Strength:{{str}} Dexterity:{{dex}} Constitution:{{con}} Intelligence:{{int}}
|
||||
Perception:{{per}} Sociability:{{soc}} Courage:{{cou}} Curiosity:{{cur}}
|
||||
Empathy:{{emp}} Temperament:{{tmp}}
|
||||
|
||||
Available materials:
|
||||
{{materials}}
|
||||
|
||||
Known items (do not reinvent):
|
||||
{{allItems}}
|
||||
|
||||
Consider 3 possible inventions using 2-3 existing materials, then pick
|
||||
the one that best matches this NPC's personality and stats.
|
||||
Use Title Case for the item name.
|
||||
|
||||
Respond with JSON:
|
||||
{"name": "Item Name", "description": "brief flavor text",
|
||||
"reasoning": "why this suits the NPC",
|
||||
"category": "resource|tool|material|structure",
|
||||
"inputs": [{"itemId": "existing_item_id", "quantity": N}],
|
||||
"workshopType": null, "toolRequired": null}
|
||||
```
|
||||
|
||||
Key differences from current prompt:
|
||||
- Full 10-stat block with readable names and scale (3-18, 10 avg)
|
||||
- "Known items" replaces separate seed/invented lists — covers both, prevents reinventing seed items
|
||||
- "Consider 3, pick 1" guides LLM reasoning without inflating response
|
||||
- Title Case instruction for consistent display names
|
||||
- `reasoning` field added for debugging/potential UI display
|
||||
|
||||
### 4. Duplicate prevention (belt + suspenders)
|
||||
|
||||
**Prompt layer**: Send all known item names (seed + invented) in the "Known items" list, so the LLM avoids reinventing existing things (including seed items like "Hammer").
|
||||
|
||||
**Validation layer**: Existing `validateInvention()` check against `itemRegistry` remains unchanged. Fixing the load bug (section 1) makes it work correctly.
|
||||
|
||||
**Race condition**: Keep existing per-entity `pendingEntities` set. Add a global `pendingItemIds` set — when the LLM returns a valid result, add its derived `itemId` to pending before registration, clear after. This prevents two NPCs from inventing the same thing in the same tick window.
|
||||
|
||||
### 5. Feature: Token usage tracking
|
||||
|
||||
- **Capture**: In `openRouterClient.ts`, extract the `usage` object from the OpenRouter API response and return it alongside the completion text.
|
||||
- **Storage**: In-memory ring buffer in `llmService.ts` — last 100 calls, each recording `{timestamp, promptTokens, completionTokens, totalTokens, templateName}`.
|
||||
- **Logging**: Per-call one-liner: `[LLM] invention: 340 in / 120 out tokens`.
|
||||
- **No persistence**: Dev/ops observability only, not game state.
|
||||
|
||||
## Files affected
|
||||
|
||||
- `server/src/game/GameLoop.ts` — re-register inventions on load
|
||||
- `server/src/llm/templates.ts` — revised invention prompt
|
||||
- `server/src/systems/inventionSystem.ts` — pass full stats, global pendingItemIds set
|
||||
- `server/src/llm/openRouterClient.ts` — return token usage from response
|
||||
- `server/src/llm/llmService.ts` — token usage ring buffer + logging
|
||||
- `server/src/industry/inventionValidator.ts` — accept optional `reasoning` field
|
||||
- `server/src/industry/inventionRegistrar.ts` — no changes expected
|
||||
- `server/src/industry/inventionParser.ts` — parse `reasoning` field
|
||||
|
||||
## Config values
|
||||
|
||||
All existing invention config values in `industryConfig.ts` remain unchanged:
|
||||
- `inventionCheckInterval: 100`
|
||||
- `inventionBaseChance: 0.005`
|
||||
- `inventionIntelligenceScale: 0.1`
|
||||
- `inventionCuriosityScale: 0.1`
|
||||
- `inventionMinChance: 0.001`
|
||||
- `inventionMaxChance: 0.05`
|
||||
Reference in New Issue
Block a user