Files
dcc-rpg/docs/development-guide.md
T
truetype eacc946868 fix: JSON repair, WS thread pool, switch to DeepSeek V4 Flash, funnel Lv0
LLM fixes:
- _try_repair_json() — ripara JSON malformato (apici, virgole trailing)
- _strip_json_from_text() — rimuove residui JSON dalla narration
- parse_tool_call() — rimuove troncamento 200 caratteri dal fallback

DM fixes:
- Fallback euristico quando params vuoti (dm.py)
- Estrazione narrazione da dm_override/narrate params.params.text

Prompt (prompts.py):
- Vietati termini tecnici/scientifici/anacronistici (HPLC, laser, ...)
- Aggiunto vocabolario fantasy obbligatorio

Server (server.py):
- dm_cycle in ThreadPoolExecutor per non bloccare WS ping
- WebSocketDisconnect gestiti
- Rimosso import inesistente db_schema

Frontend (index.html):
- Nascosti tool name dalla chat
- Personaggio -> PG, connessioni aree con nomi

Infrastruttura:
- Switch da openrouter/free a DeepSeek V4 Flash via OpenCode Go
- pyproject.toml, Dockerfile, docker-compose.yml, start.sh
- .env auto-load (start.sh)
- Funnel Lv0 (funnel.py) — creazione PG livello 0

Docs: architecture, database, setup, game mechanics, codebase ref
2026-06-28 08:18:24 +02:00

6.5 KiB

Development Guide

Come Aggiungere un Nuovo Tool

  1. Apri src/engine/tools.py
  2. Scrivi una funzione con @tool decorator:
@tool
def perception_check(character_id: int, dc: int, modifier: int = 0) -> ToolResult:
    char = storage.get_character(character_id)
    if not char:
        return {"success": False, "tool": "perception-check", "message": "PG non trovato."}
    atk = dice.roll("d20", modifier=modifier)
    passed = atk["total"] >= dc
    return {
        "success": True,
        "tool": "perception-check",
        "character_id": character_id,
        "roll": atk["total"],
        "dc": dc,
        "passed": passed,
        "message": f"Percezione: d20+{modifier} → {atk['total']} vs CD {dc} → {'Successo!' if passed else 'Fallimento.'}",
    }
  1. Aggiungi il tool a _KNOWN_TOOLS in src/engine/prompts.py:
"perception_check": {"character_id": "id pg", "dc": "CD percezione", "modifier": "bonus/penalità"},
  1. Aggiungi un esempio a TOOL_EXAMPLES in prompts.py:
- Tiro percezione CD 12 → {"tool": "perception_check", "params": {"character_id": 1, "dc": 12}, "narration": "Tendi l'orecchio..."}
  1. Fatto. Il tool viene automaticamente registrato in TOOL_REGISTRY.

Come Aggiungere un Provider LLM

  1. Apri src/engine/providers.py
  2. Crea una nuova classe che estende LLMProvider:
class AnthropicProvider(LLMProvider):
    def __init__(self, api_key: str, model: str, timeout: int = 60):
        self.api_key = api_key
        self.model = model
        self.timeout = timeout

    def ask(self, prompt: str, system: str = "") -> str:
        # Implementa chiamata API Anthropic qui
        ...
  1. Registra in _registry:
_registry = {
    "ollama": OllamaProvider,
    "openrouter": OpenRouterProvider,
    "local": LocalProvider,
    "anthropic": AnthropicProvider,  # <-- aggiungi
}
  1. Aggiungi il case in create_provider():
if cfg.kind == "anthropic":
    return cls(cfg.anthropic_api_key, cfg.anthropic_model, cfg.timeout)
  1. Aggiungi configurazione in config.py:
anthropic_api_key: str = ""
anthropic_model: str = "claude-sonnet-4-20250514"

Come Aggiungere una Nuova Area all'Avventura

  1. Apri data/adventure/avventura.json
  2. Aggiungi area all'array areas:
{
  "id": "1.10",
  "name": "Biblioteca Arcana",
  "order": 10,
  "description": "...",
  "atmosphere": "...",
  "connections": ["1.9"],
  "features": ["Scaffali polverosi", "Globo luminoso fluttuante"],
  "puzzles": [...],
  "traps": [],
  "monsters": [{"ref_id": "ancient_spirit", "count": 1, "tactics": "...", "ambush": false}],
  "items": [
    {"id": "scroll_of_truth", "name": "Pergamena della Verità", "description": "...", "type": "consumable", "magical": true}
  ],
  "treasure": []
}
  1. Aggiungi stat block del mostro in monsters:
{
  "id": "ancient_spirit",
  "name": "Spirito Antico",
  "level": 3,
  "stats": {"ac": 14, "hp": 18, "attack": "d20+3", "damage": "1d6+1", "saves": {"Tempra": 2, "Riflessi": 4, "Volontà": 6}, "move": "9m (volando)"},
  "special": "Intangibile, colpito solo da magia o argento",
  "xp": 100,
  "loot": []
}
  1. Riavvia il server. La nuova area viene caricata automaticamente.

Come Aggiungere una Tabella Crit/Fumble

  1. Aggiungi entry a data/crit_fumble_tables.json
  2. Riavvia il server (ricarica da JSON se DB vuoto)
  3. Se il DB è già popolato, cancella data/dcc_rpg.db (verrà ricreato)

Test

# Test dadi
python src/engine/dice.py

# Test tools (con setup DB)
python src/engine/tools.py

# Test LLM parsing
python src/engine/llm.py

# Test DB completo
python src/engine/db.py

# Avvia CLI interattiva
python -c "
from src.engine import dm, db
db.init_db()
camp_id = db.create_campaign('Test', 'Avventura')
char_id = db.create_character(camp_id, 'Eroe', 'guerriero', 'soldato')
db.update_character(char_id, level=1, str=15, agi=11, sta=14, per=9, int=8, lck=10, hp_current=12, hp_max=12, ac=15)
dm.interactive(camp_id)
"

Test esistenti (tests/)

  • test_dice.py — test modulo dadi (pytest)
  • test_tools.py — test tool execution
  • test_integration.py — test flusso dm_cycle
  • test_websocket.py — test WebSocket lifecycle

Per eseguire:

python -m pytest tests/ -v

Known LSP Issues (non bloccanti)

Il LSP segnala questi errori, ma non impediscono l'esecuzione:

File Errore Causa
db.py:762 Object of type "None" is not subscriptable cursor.fetchone() in get_current_area può restituire None
test_dice.py:47 output_die not in ValidationError DiceChainResult e ValidationError condividono TypedDict — meglio @dataclass
tests/test_websocket.py:8 Import pytest could not be resolved pytest non installato nell'ambiente LSP
game_context.py:29 get_current_area not in db LSP non riconosce funzioni dinamicamente caricate

Estendere il Frontend

Il frontend è un singolo file HTML (src/web/static/index.html) con:

  • CSS: tema scuro sword & sorcery con variabili CSS
  • JS: ~300 righe di vanilla JS (nessun framework)
  • WebSocket: connessione persistente, auto-reconnect

Per modificare:

  • Stile: modifica le variabili CSS in :root e classi dei componenti
  • Nuova sezione sidebar: aggiungi div in HTML + funzione render in JS
  • Nuovo tipo di messaggio: aggiungi case a handleMessage()

Guida alla Conoscenza Base (knowledge/)

I 15 file in knowledge/ contengono le regole DCC in formato markdown italiano. Non sono usati direttamente dal runtime (i dati sono in DB/JSON/code), ma sono la fonte di verità per lo sviluppo.

Per rigenerare prompt alla luce delle regole:

  1. Consulta i file knowledge/01_meccanica_base.md → knowledge/15_dadi.md
  2. Adatta prompts.py e tools.py di conseguenza

Checklist Prossimi Miglioramenti

Priorità alta:

  • Aggiungere requirements.txt o pyproject.toml
  • Aggiungere Dockerfile + docker-compose.yml
  • Script start.sh per avvio con un comando
  • Supporto per più PG contemporaneamente
  • Persistenza PF multipli mostri per area

Priorità media:

  • Test completi per ogni tool
  • Tavola incantesimi dal Reference Booklet
  • Abilità ladro complete (dado dedicato per livello)
  • Scacciare non-morti (Chierico)
  • Equipaggiamento e armi dal knowledge base

Priorità bassa:

  • OCR Core Rulebook (498 pagine immagini, ~8h stimate)
  • RAG + embedding per knowledge base DCC
  • Multi-giocatore (più PG in contemporanea)
  • Inventory drag & drop nel frontend
  • Mappa interattiva delle aree