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
6.5 KiB
6.5 KiB
Development Guide
Come Aggiungere un Nuovo Tool
- Apri
src/engine/tools.py - Scrivi una funzione con
@tooldecorator:
@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.'}",
}
- Aggiungi il tool a
_KNOWN_TOOLSinsrc/engine/prompts.py:
"perception_check": {"character_id": "id pg", "dc": "CD percezione", "modifier": "bonus/penalità"},
- Aggiungi un esempio a
TOOL_EXAMPLESinprompts.py:
- Tiro percezione CD 12 → {"tool": "perception_check", "params": {"character_id": 1, "dc": 12}, "narration": "Tendi l'orecchio..."}
- Fatto. Il tool viene automaticamente registrato in
TOOL_REGISTRY.
Come Aggiungere un Provider LLM
- Apri
src/engine/providers.py - 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
...
- Registra in
_registry:
_registry = {
"ollama": OllamaProvider,
"openrouter": OpenRouterProvider,
"local": LocalProvider,
"anthropic": AnthropicProvider, # <-- aggiungi
}
- Aggiungi il case in
create_provider():
if cfg.kind == "anthropic":
return cls(cfg.anthropic_api_key, cfg.anthropic_model, cfg.timeout)
- Aggiungi configurazione in
config.py:
anthropic_api_key: str = ""
anthropic_model: str = "claude-sonnet-4-20250514"
Come Aggiungere una Nuova Area all'Avventura
- Apri
data/adventure/avventura.json - 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": []
}
- 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": []
}
- Riavvia il server. La nuova area viene caricata automaticamente.
Come Aggiungere una Tabella Crit/Fumble
- Aggiungi entry a
data/crit_fumble_tables.json - Riavvia il server (ricarica da JSON se DB vuoto)
- 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 executiontest_integration.py— test flusso dm_cycletest_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
:roote 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:
- Consulta i file
knowledge/01_meccanica_base.md→knowledge/15_dadi.md - Adatta
prompts.pyetools.pydi 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