From bc0e1154b52f157095d9682bc08f2f2e6cd3fef8 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 9 Mar 2026 20:15:34 +0000 Subject: [PATCH] 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 --- ...2026-03-09-invention-refinements-design.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/plans/2026-03-09-invention-refinements-design.md diff --git a/docs/plans/2026-03-09-invention-refinements-design.md b/docs/plans/2026-03-09-invention-refinements-design.md new file mode 100644 index 0000000..6d7e12a --- /dev/null +++ b/docs/plans/2026-03-09-invention-refinements-design.md @@ -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`