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
8.4 KiB
8.4 KiB
Codebase Reference
Directory Layout
rpg/
├── src/
│ ├── engine/
│ │ ├── __init__.py # Public API del modulo engine
│ │ ├── config.py # ProviderConfig, load_from_env()
│ │ ├── db.py # SQLite schema, CRUD, adventure data access
│ │ ├── dice.py # DCC dice system (roll, dice_chain)
│ │ ├── dm.py # DM cycle orchestrator + CLI interactive
│ │ ├── game_context.py # GameContext dataclass (campagna + stato)
│ │ ├── heuristic_fallback.py# Keyword parser per fallback LLM
│ │ ├── llm.py # LLM abstraction, ask(), parse_tool_call()
│ │ ├── prompts.py # System prompt, context builder, tool docs
│ │ ├── providers.py # Ollama/OpenRouter/Local providers
│ │ └── tools.py # @tool registry, 10 Approved Tools
│ │
│ └── web/
│ ├── __init__.py
│ ├── server.py # FastAPI app, REST, WebSocket, main()
│ └── static/
│ └── index.html # SPA frontend (888 righe CSS+JS+HTML)
│
├── data/
│ ├── dcc_rpg.db # SQLite database (auto-creato)
│ ├── adventure/
│ │ └── avventura.json # Avventura machine-readable (9 aree)
│ └── crit_fumble_tables.json # Tabelle critici e fumble (207 righe)
│
├── docs/
│ └── plans/
│ └── 2026-06-25-dcc-rpg-design.md # Design doc originale (314 righe)
│
├── docs_dev/ # Documentazione sviluppo (questo)
├── knowledge/ # 15 file KB (regole DCC, ~3708 righe)
├── input/ # PDF sorgente, reference booklet
├── ocr_full/ # OCR output (58K parole)
├── graphify-out/ # Grafo concettuale
└── tests/ # Test (pytest)
├── test_dice.py
├── test_tools.py
├── test_integration.py
└── test_websocket.py
Modulo: src/engine/config.py
ProviderKind = "ollama" | "openrouter" | "local"
ProviderConfig (dataclass)
| Campo | Default | Env Var |
|---|---|---|
kind |
"openrouter" |
DCC_LLM_PROVIDER |
ollama_base_url |
http://192.168.0.50:11434 |
DCC_OLLAMA_URL |
ollama_model |
gemma2:9b |
DCC_OLLAMA_MODEL |
openrouter_api_key |
"" |
DCC_OPENROUTER_KEY |
openrouter_model |
"openrouter/free" |
DCC_OPENROUTER_MODEL |
openrouter_free_fallback |
True |
DCC_OR_FREE_FALLBACK |
local_base_url |
http://192.168.0.240/v1 |
DCC_LOCAL_URL |
local_api_key |
"" |
DCC_LOCAL_KEY |
local_model |
"big-pickle" |
DCC_LOCAL_MODEL |
timeout |
60 |
DCC_LLM_TIMEOUT |
Modulo: src/engine/dice.py
DICE_CHAIN
d3 → d4 → d5 → d6 → d7 → d8 → d10 → d12 → d14 → d16 → d20 → d24 → d30
SPECIAL_DICE
d% (100), d100 (100), d0 (0), d1 (1), d2 (2)
API
| Funzione | Parametri | Returns |
|---|---|---|
roll(die, count=1, modifier=0) |
die: str | RollResult {die, count, modifier, results[], total, crit, fumble, raw} |
dice_chain(die, steps) |
steps: int (+=su, -=giù) | DiceChainResult {input_die, steps, output_die, chain} o ValidationError |
roll_str(raw) |
"2d20+3" | RollResult |
validate_die(die) |
die: str | `str |
Regole crit/fumble
- Solo su
d20: 20 = crit, 1 = fumble - Critico: moltiplica danno, tiro su tabella critici per classe
- Fumble: tiro su tabella fumble (dado dipende da armatura)
Modulo: src/engine/tools.py
@tool decorator
Ogni funzione decorata viene automaticamente registrata in TOOL_REGISTRY.
run(tool_name, **kwargs) — dispatcher centralizzato
Cerca in TOOL_REGISTRY, chiama la funzione, cattura eccezioni.
10 Approved Tools
| Tool | Funzione | Parametri Chiave |
|---|---|---|
roll_dice |
Tira dado/i | die, count, modifier |
dice_chain |
Risolve catena dadi | die, steps |
combat |
Ciclo combattimento completo | character_id, target_ac, attack_bonus, damage_die, mighty_deed_die, monster_id, armor_type |
ability_check |
Prova abilità | character_id, dc, modifier |
saving_throw |
Tiro salvezza | character_id, save_type (Tempra/Riflessi/Volontà), dc |
movement |
Movimento | distance_m, terrain, stealth |
spellcasting |
Incantesimo | character_id, spell_name, spell_check, dc |
update_inventory |
Gestione inventario | character_id, item, action (add/remove/equip/unequip/list) |
award_xp |
Assegna PE | character_id, xp |
dm_override |
DM override | command, params |
Tool combat — Dettaglio flusso
- Carica PG e mostro (con PF persistenti da combat_state)
- Tiro iniziativa (d20)
- Tiro attacco (d20 + bonus)
- Mighty Deed (se guerriero, dado d3/d4/d5 aggiuntivo)
- Crit/Fumble su 20/1 naturale
- Lookup tabella critici (per classe/livello) o fumble (per armatura)
- Calcolo danno + danno extra da critico
- Persistenza PF mostro in combat_state
Tool spellcasting — Tier
| Tiro | Risultato |
|---|---|
| 1 | Fallimento critico (corruzione!) |
| < DC | Fallimento |
| ≤ 11 | Successo minore (effetto base) |
| ≤ 17 | Successo moderato (effetto migliorato) |
| ≤ 19 | Successo maggiore (effetto potente) |
| 20+ | Successo massimo (effetto devastante) |
Modulo: src/engine/providers.py
Provider Classes
| Classe | Endpoint | Auth |
|---|---|---|
OllamaProvider |
{base_url}/api/generate |
Nessuna |
OpenRouterProvider |
openrouter.ai/api/v1/chat/completions |
Bearer API key |
LocalProvider |
{base_url}/chat/completions |
Bearer key (opzionale) |
create_provider(cfg) → Factory
Legge cfg.kind, istanzia provider corretto con parametri appropriati.
Modulo: src/engine/dm.py
dm_cycle(campaign_id, player_action) → ciclo principale
GameContext.load(campaign_id)— assembla contesto_llm_decision(ctx, action)— LLM + fallbacktools.run(tool_name, **params)— esecuzionedb.advance_turn()+db.log_turn()— persistenza- Return response dict
interactive(campaign_id) — CLI interattiva
Loop input/output con prompt `⚔️ », comandi: esci, stato
Modulo: src/engine/prompts.py
SYSTEM_PROMPT — Prompt di sistema (~60 righe)
- Stile narrazione
- Meccaniche base DCC
- Tool disponibili con parametri (json)
- Esempi di chiamata
- Formato risposta obbligatorio
build_prompt(ctx, player_action, current_area_id) → prompt completo
format_context(ctx): contesto campagnaformat_area_context(area_id): descrizione area attuale- Azione giocatore
Modulo: src/engine/db.py
Vedi database-schema.md per schema completo.
Lifecycle
init_db()→ init_schema + migrate + populate_from_json- Connessioni thread-local con
threading.local() - WAL mode + busy_timeout per concorrenza
Modulo: src/web/server.py
Entry Point
python -m src.web.server
# oppure uvicorn src.web.server:app --host 0.0.0.0 --port 8300
REST Endpoints
| Metodo | Path | Descrizione |
|---|---|---|
| GET | /api/campaigns |
Lista campagne |
| GET | /api/campaign/{id} |
Stato campagna completo |
| POST | /api/campaign?name=... |
Crea campagna |
| POST | /api/campaign/{id}/character?name=...&char_class=... |
Aggiunge PG default |
| GET | /health |
Health check |
| GET | / |
Frontend SPA |
WebSocket /ws/{campaign_id}
| Messaggio | Direzione | Descrizione |
|---|---|---|
{"type": "action", "payload": "..."} |
Client→Server | Azione giocatore |
{"type": "ping"} |
Client→Server | Keepalive |
{"type": "state", "campaign": {...}} |
Server→Client | Stato completo |
{"type": "response", "turn": N, ...} |
Server→Client | Risultato turno |
{"type": "pong"} |
Server→Client | Keepalive response |
{"type": "error", "message": "..."} |
Server→Client | Errore |
File: src/web/static/index.html
Single-page application (~888 righe) con:
- Welcome screen: lista campagne, creazione nuova
- Sidebar (320px): scheda PG (statistiche, HP bar), missioni, mappa aree
- Chat area: messaggi player/DM con stili differenziati
- Input bar: campo testo + bottone invio
- WebSocket con auto-reconnect ogni 3s
- Tema scuro "sword & sorcery" con font Cinzel + Cormorant Garamond