feat: dashboard VAULTCOM v3 + config.py TICK_SLEEP_SEC + sim_config
- dashboard/app.py: Nouveau dashboard Flask (schéma DB migration_003) Affiche PNJ par tier (0/1/2/3), events, rencontres, world_state, faction_relations, inventaire boss. Auto-refresh 30s. Connecté via tunnel SSH interne vers Vigile:5432 - src/engine/config.py: Fix TICK_SLEEP_SEC depuis env (était hardcodé 0) - src/config/sim_001.json: Paramètres simulation centralisés (tick_speed, difficulty presets, LLM config, multi-session) Accessible: fallout.coyoteos.ovh
This commit is contained in:
@@ -0,0 +1,431 @@
|
||||
"""
|
||||
VAULTCOM — Fallout: Venice of Wasteland
|
||||
Dashboard simulation. Schéma DB actuel (migration_003).
|
||||
"""
|
||||
|
||||
import os, json
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
from flask import Flask, render_template_string, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
DB_HOST = os.getenv("DB_HOST", "localhost")
|
||||
DB_PORT = int(os.getenv("DB_PORT", 5432))
|
||||
DB_NAME = os.getenv("DB_NAME", "fallout")
|
||||
DB_USER = os.getenv("DB_USER", "fallout")
|
||||
DB_PASS = os.getenv("DB_PASS", "VeniceOfWasteland2026!")
|
||||
|
||||
def get_conn():
|
||||
return psycopg2.connect(
|
||||
host=DB_HOST, port=DB_PORT, dbname=DB_NAME,
|
||||
user=DB_USER, password=DB_PASS
|
||||
)
|
||||
|
||||
def query(sql, params=None):
|
||||
conn = get_conn()
|
||||
try:
|
||||
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
|
||||
cur.execute(sql, params)
|
||||
return [dict(r) for r in cur.fetchall()]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def query_one(sql, params=None):
|
||||
rows = query(sql, params)
|
||||
return rows[0] if rows else None
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# HTML template
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
TEMPLATE = r"""<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<title>VAULTCOM — Fallout: Venice of Wasteland</title>
|
||||
<style>
|
||||
:root {
|
||||
--pip-green: #00ff41;
|
||||
--pip-amber: #ffb347;
|
||||
--pip-red: #ff4444;
|
||||
--pip-blue: #44aaff;
|
||||
--pip-dim: #003300;
|
||||
--bg: #0a0a0a;
|
||||
--panel: #0d1a0d;
|
||||
--border: #1a3d1a;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--pip-green);
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
padding: 8px;
|
||||
}
|
||||
h1 { font-size: 20px; letter-spacing: 4px; text-align: center; padding: 10px 0;
|
||||
border-bottom: 1px solid var(--pip-green); margin-bottom: 10px; }
|
||||
.header-bar { display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 4px 8px; background: var(--pip-dim); border: 1px solid var(--border);
|
||||
margin-bottom: 10px; font-size: 11px; color: var(--pip-amber); }
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; margin-top: 10px; }
|
||||
.panel { background: var(--panel); border: 1px solid var(--border); padding: 8px; }
|
||||
.panel h2 { font-size: 12px; letter-spacing: 2px; color: var(--pip-amber);
|
||||
border-bottom: 1px solid var(--border); padding-bottom: 4px; margin-bottom: 6px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th { color: var(--pip-amber); font-size: 11px; padding: 3px 4px; text-align: left;
|
||||
border-bottom: 1px solid var(--border); }
|
||||
td { padding: 2px 4px; font-size: 11px; border-bottom: 1px dashed #0f2a0f; vertical-align: top; }
|
||||
tr:hover td { background: #0f200f; }
|
||||
.tier0 { color: #ff6600; }
|
||||
.tier1 { color: var(--pip-green); }
|
||||
.tier2 { color: var(--pip-blue); }
|
||||
.tier3 { color: #aaaaaa; }
|
||||
.dead { color: #444; text-decoration: line-through; }
|
||||
.hp-bar { display: inline-block; width: 60px; height: 8px; background: #111; border: 1px solid #333; vertical-align: middle; }
|
||||
.hp-fill { height: 100%; background: var(--pip-green); }
|
||||
.hp-low { background: var(--pip-amber); }
|
||||
.hp-crit { background: var(--pip-red); }
|
||||
.badge { display: inline-block; padding: 1px 5px; font-size: 10px; border-radius: 2px; margin: 1px; }
|
||||
.badge-union { background: #1a3a5c; color: #66aaff; }
|
||||
.badge-cda { background: #3a1a1a; color: #ff6666; }
|
||||
.badge-ecumeurs { background: #2a1a3a; color: #cc88ff; }
|
||||
.badge-syndicat { background: #3a3a1a; color: #ffee66; }
|
||||
.badge-default { background: #1a1a1a; color: #888; }
|
||||
.event-combat { color: var(--pip-red); }
|
||||
.event-encounter { color: var(--pip-amber); }
|
||||
.event-survival { color: #88cc88; }
|
||||
.event-death { color: #ff0000; font-weight: bold; }
|
||||
.event-loot { color: #88aaff; }
|
||||
.relation-hostile { color: var(--pip-red); }
|
||||
.relation-neutral { color: #888; }
|
||||
.relation-allie { color: var(--pip-green); }
|
||||
.scrollable { max-height: 280px; overflow-y: auto; }
|
||||
.world-zone { display: flex; justify-content: space-between; padding: 3px 0;
|
||||
border-bottom: 1px dashed #0f2a0f; font-size: 11px; }
|
||||
.zone-name { color: var(--pip-amber); min-width: 160px; }
|
||||
.level-bar { display: inline-block; width: 40px; height: 6px; background: #111;
|
||||
border: 1px solid #333; vertical-align: middle; }
|
||||
.level-fill { height: 100%; background: var(--pip-green); }
|
||||
.stat-block { display: inline-flex; gap: 4px; }
|
||||
.stat-item { font-size: 10px; color: #888; }
|
||||
.stat-item span { color: var(--pip-green); }
|
||||
.footer { text-align: center; font-size: 10px; color: #333; padding: 10px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>⚡ VAULTCOM — VENICE OF WASTELAND ⚡</h1>
|
||||
|
||||
{% if session %}
|
||||
<div class="header-bar">
|
||||
<span>SESSION #{{ session.id }} — {{ session.name }}</span>
|
||||
<span>JOUR {{ session.current_day }} / TICK {{ session.current_tick }}</span>
|
||||
<span>MODE: {{ session.mode | upper }} | STATUS: {{ session.status | upper }}</span>
|
||||
<span>SEED: {{ session.seed_global }}</span>
|
||||
<span>🔄 Actualisé toutes les 30s</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<!-- PNJ par tier -->
|
||||
<div class="panel">
|
||||
<h2>👥 PNJ ACTIFS ({{ alive_count }}/{{ total_count }})</h2>
|
||||
<div class="scrollable">
|
||||
<table>
|
||||
<tr><th>TIER</th><th>NOM</th><th>PV</th><th>FACTION</th><th>ZONE</th><th>CAPS</th></tr>
|
||||
{% for c in characters %}
|
||||
<tr class="{{ 'dead' if not c.is_alive else 'tier' ~ (c.tier or 1) }}">
|
||||
<td>
|
||||
{% if c.tier == 0 %}<span class="tier0">★BOSS</span>
|
||||
{% elif c.tier == 2 %}<span class="tier2">◈ T2</span>
|
||||
{% elif c.tier == 3 %}<span class="tier3">· T3</span>
|
||||
{% else %}<span class="tier1">● T1</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ c.name }}</td>
|
||||
<td>
|
||||
{% if c.is_alive %}
|
||||
{% set pct = (c.hp / c.max_hp * 100) | int if c.max_hp else 0 %}
|
||||
<div class="hp-bar"><div class="hp-fill {{ 'hp-crit' if pct < 25 else 'hp-low' if pct < 50 else '' }}" style="width:{{ pct }}%"></div></div>
|
||||
<small> {{ c.hp }}/{{ c.max_hp }}</small>
|
||||
{% else %}†{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% set f = c.faction_slug or 'sans' %}
|
||||
<span class="badge badge-{{ 'union' if 'union' in f else 'cda' if 'cda' in f else 'ecumeurs' if 'ecumeur' in f else 'default' }}">
|
||||
{{ f[:12] }}
|
||||
</span>
|
||||
</td>
|
||||
<td><small>{{ (c.location_slug or '—')[:14] }}</small></td>
|
||||
<td>{{ c.caps }}¢</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Événements récents -->
|
||||
<div class="panel">
|
||||
<h2>📡 ÉVÉNEMENTS RÉCENTS (J{{ session.current_day if session else '?' }})</h2>
|
||||
<div class="scrollable">
|
||||
<table>
|
||||
<tr><th>J/T</th><th>TYPE</th><th>DESCRIPTION</th></tr>
|
||||
{% for ev in events %}
|
||||
<tr>
|
||||
<td class="tier3">{{ ev.day }}/{{ ev.tick }}</td>
|
||||
<td class="event-{{ ev.event_type }}"><small>{{ ev.event_type }}</small></td>
|
||||
<td><small>{{ ev.description[:80] }}{% if ev.description|length > 80 %}…{% endif %}</small></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not events %}<tr><td colspan="3" style="color:#444;text-align:center">Aucun événement</td></tr>{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /grid -->
|
||||
|
||||
<div class="grid3">
|
||||
|
||||
<!-- World state par zone -->
|
||||
<div class="panel">
|
||||
<h2>🗺 ÉTAT DU MONDE</h2>
|
||||
<div class="scrollable">
|
||||
{% for ws in world_state %}
|
||||
<div class="world-zone">
|
||||
<span class="zone-name">{{ ws.location_slug }}</span>
|
||||
<span class="stat-block">
|
||||
<span class="stat-item">🍖<div class="level-bar"><div class="level-fill" style="width:{{ ws.food_level or 0 }}%"></div></div></span>
|
||||
<span class="stat-item">💧<div class="level-bar"><div class="level-fill" style="width:{{ ws.water_level or 0 }}%"></div></div></span>
|
||||
<span class="stat-item">🛡<div class="level-bar"><div class="level-fill" style="width:{{ ws.security_level or 0 }}%"></div></div></span>
|
||||
<span class="stat-item">👤<span>{{ ws.pop_count or 0 }}</span></span>
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not world_state %}<p style="color:#444;font-size:11px">Pas de données monde</p>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Relations faction -->
|
||||
<div class="panel">
|
||||
<h2>⚔ RELATIONS FACTIONS</h2>
|
||||
<div class="scrollable">
|
||||
<table>
|
||||
<tr><th>FACTION A</th><th>FACTION B</th><th>SCORE</th><th>ÉTAT</th></tr>
|
||||
{% for r in faction_relations %}
|
||||
<tr>
|
||||
<td><small>{{ r.faction_a[:12] }}</small></td>
|
||||
<td><small>{{ r.faction_b[:12] }}</small></td>
|
||||
<td class="{{ 'relation-hostile' if r.relation_score < -20 else 'relation-allie' if r.relation_score > 20 else 'relation-neutral' }}">
|
||||
{{ r.relation_score }}
|
||||
</td>
|
||||
<td><small class="{{ 'relation-hostile' if r.relation_score < -20 else 'relation-allie' if r.relation_score > 20 else 'relation-neutral' }}">
|
||||
{{ r.relation_label or ('HOSTILE' if r.relation_score < -20 else 'ALLIÉ' if r.relation_score > 20 else 'NEUTRE') }}
|
||||
</small></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not faction_relations %}<tr><td colspan="4" style="color:#444">Pas de données</td></tr>{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stats simulation -->
|
||||
<div class="panel">
|
||||
<h2>📊 STATS SIMULATION</h2>
|
||||
<table>
|
||||
<tr><th>MÉTRIQUE</th><th>VALEUR</th></tr>
|
||||
{% for k, v in stats.items() %}
|
||||
<tr><td>{{ k }}</td><td class="tier1">{{ v }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<h2 style="margin-top:6px">🎭 PNJ PAR TIER</h2>
|
||||
<table>
|
||||
<tr><th>TIER</th><th>LABEL</th><th>VIVANTS</th><th>MORTS</th></tr>
|
||||
{% for row in tier_stats %}
|
||||
<tr class="tier{{ row.tier }}">
|
||||
<td>{{ row.tier }}</td>
|
||||
<td>{{ row.label }}</td>
|
||||
<td>{{ row.alive }}</td>
|
||||
<td class="tier3">{{ row.dead }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<br>
|
||||
<h2 style="margin-top:6px">🏙 PNJ PAR ZONE (TOP 5)</h2>
|
||||
<table>
|
||||
<tr><th>ZONE</th><th>PNJ</th></tr>
|
||||
{% for row in zone_stats %}
|
||||
<tr><td><small>{{ row.location_slug }}</small></td><td>{{ row.cnt }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /grid3 -->
|
||||
|
||||
<!-- Encounters récents -->
|
||||
<div class="panel" style="margin-top:10px">
|
||||
<h2>⚡ RENCONTRES RÉCENTES</h2>
|
||||
<div class="scrollable" style="max-height:160px">
|
||||
<table>
|
||||
<tr><th>J/T</th><th>TYPE</th><th>ACTEUR</th><th>ZONE</th><th>DESCRIPTION</th></tr>
|
||||
{% for ev in encounters %}
|
||||
<tr>
|
||||
<td class="tier3">{{ ev.day }}/{{ ev.tick }}</td>
|
||||
<td class="event-encounter"><small>{{ ev.event_type }}</small></td>
|
||||
<td><small>{{ ev.actor_name or '—' }}</small></td>
|
||||
<td><small>{{ (ev.location_slug or '—')[:14] }}</small></td>
|
||||
<td><small>{{ ev.description[:90] }}{% if ev.description|length > 90 %}…{% endif %}</small></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not encounters %}<tr><td colspan="5" style="color:#444;text-align:center">Aucune rencontre</td></tr>{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inventaire boss (tier 0) -->
|
||||
{% if boss_inventory %}
|
||||
<div class="panel" style="margin-top:10px">
|
||||
<h2>★ ÉQUIPEMENT CHEFS DE FACTION</h2>
|
||||
<table>
|
||||
<tr><th>BOSS</th><th>ITEM</th><th>TYPE</th><th>QTÉ</th><th>ÉQUIPÉ</th></tr>
|
||||
{% for row in boss_inventory %}
|
||||
<tr>
|
||||
<td class="tier0">{{ row.char_name }}</td>
|
||||
<td>{{ row.item_name }}</td>
|
||||
<td class="tier3"><small>{{ row.item_type }}</small></td>
|
||||
<td>{{ row.quantity }}</td>
|
||||
<td>{{ '✓' if row.is_equipped else '' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="footer">VAULT-TEC VAULTCOM v3.0 | Fallout: Venice of Wasteland | J={{ session.current_day if session else '?' }} T={{ session.current_tick if session else '?' }}</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Routes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
session = query_one("SELECT * FROM sessions ORDER BY id DESC LIMIT 1")
|
||||
sid = session["id"] if session else 1
|
||||
|
||||
characters = query("""
|
||||
SELECT c.*, COALESCE(c.tier, 1) as tier
|
||||
FROM characters c
|
||||
WHERE c.session_id = %s
|
||||
ORDER BY COALESCE(c.tier,1), c.is_alive DESC, c.name
|
||||
""", (sid,))
|
||||
|
||||
alive_count = sum(1 for c in characters if c["is_alive"])
|
||||
total_count = len(characters)
|
||||
|
||||
events = query("""
|
||||
SELECT e.*, c.name as actor_name
|
||||
FROM events e
|
||||
LEFT JOIN characters c ON c.id = e.actor_id
|
||||
WHERE e.session_id = %s
|
||||
AND e.event_type NOT IN ('encounter_lambda','encounter_group','encounter_actif','encounter_boss')
|
||||
ORDER BY e.day DESC, e.tick DESC, e.id DESC
|
||||
LIMIT 50
|
||||
""", (sid,))
|
||||
|
||||
encounters = query("""
|
||||
SELECT e.*, c.name as actor_name
|
||||
FROM events e
|
||||
LEFT JOIN characters c ON c.id = e.actor_id
|
||||
WHERE e.session_id = %s
|
||||
AND e.event_type LIKE 'encounter%%'
|
||||
ORDER BY e.day DESC, e.tick DESC, e.id DESC
|
||||
LIMIT 30
|
||||
""", (sid,))
|
||||
|
||||
world_state = query("""
|
||||
SELECT DISTINCT ON (location_slug) *
|
||||
FROM world_state
|
||||
WHERE session_id = %s
|
||||
ORDER BY location_slug, day DESC
|
||||
""", (sid,))
|
||||
|
||||
faction_relations = query("""
|
||||
SELECT * FROM faction_relations
|
||||
WHERE session_id = %s
|
||||
ORDER BY relation_score ASC
|
||||
""", (sid,))
|
||||
|
||||
tier_labels = {0: "BOSS", 1: "ACTIFS SIM", 2: "PNJ+", 3: "PASSAGE"}
|
||||
tier_raw = query("""
|
||||
SELECT COALESCE(tier,1) as tier,
|
||||
COUNT(*) FILTER (WHERE is_alive) as alive,
|
||||
COUNT(*) FILTER (WHERE NOT is_alive) as dead
|
||||
FROM characters WHERE session_id = %s
|
||||
GROUP BY COALESCE(tier,1)
|
||||
ORDER BY COALESCE(tier,1)
|
||||
""", (sid,))
|
||||
tier_stats = [{"tier": r["tier"], "label": tier_labels.get(r["tier"],"?"),
|
||||
"alive": r["alive"], "dead": r["dead"]} for r in tier_raw]
|
||||
|
||||
zone_stats = query("""
|
||||
SELECT location_slug, COUNT(*) as cnt
|
||||
FROM characters
|
||||
WHERE session_id = %s AND is_alive = TRUE
|
||||
GROUP BY location_slug
|
||||
ORDER BY cnt DESC
|
||||
LIMIT 5
|
||||
""", (sid,))
|
||||
|
||||
events_total = query_one("SELECT COUNT(*) as n FROM events WHERE session_id=%s", (sid,))
|
||||
combat_total = query_one("SELECT COUNT(*) as n FROM events WHERE session_id=%s AND event_type='combat'", (sid,))
|
||||
deaths_total = query_one("SELECT COUNT(*) as n FROM events WHERE session_id=%s AND event_type='death'", (sid,))
|
||||
enc_total = query_one("SELECT COUNT(*) as n FROM events WHERE session_id=%s AND event_type LIKE 'encounter%%'", (sid,))
|
||||
|
||||
stats = {
|
||||
"Événements total": events_total["n"] if events_total else 0,
|
||||
"Combats": combat_total["n"] if combat_total else 0,
|
||||
"Morts": deaths_total["n"] if deaths_total else 0,
|
||||
"Rencontres": enc_total["n"] if enc_total else 0,
|
||||
"PNJ vivants": alive_count,
|
||||
"PNJ morts": total_count - alive_count,
|
||||
}
|
||||
|
||||
boss_inventory = query("""
|
||||
SELECT c.name as char_name, i.item_name, i.item_type, i.quantity, i.is_equipped
|
||||
FROM inventory i
|
||||
JOIN characters c ON c.id = i.character_id
|
||||
WHERE c.session_id = %s AND COALESCE(c.tier,1) = 0
|
||||
ORDER BY c.name, i.is_equipped DESC
|
||||
""", (sid,))
|
||||
|
||||
return render_template_string(TEMPLATE,
|
||||
session=session,
|
||||
characters=characters,
|
||||
alive_count=alive_count,
|
||||
total_count=total_count,
|
||||
events=events,
|
||||
encounters=encounters,
|
||||
world_state=world_state,
|
||||
faction_relations=faction_relations,
|
||||
tier_stats=tier_stats,
|
||||
zone_stats=zone_stats,
|
||||
stats=stats,
|
||||
boss_inventory=boss_inventory,
|
||||
)
|
||||
|
||||
@app.route("/api/state")
|
||||
def api_state():
|
||||
session = query_one("SELECT * FROM sessions ORDER BY id DESC LIMIT 1")
|
||||
sid = session["id"] if session else 1
|
||||
alive = query("SELECT id, name, hp, max_hp, tier, faction_slug, location_slug FROM characters WHERE session_id=%s AND is_alive=TRUE", (sid,))
|
||||
return jsonify({"session": session, "alive_pnj": alive})
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=False)
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"_comment": "Configuration de la simulation session 1 — Venice of Wasteland",
|
||||
"session": {
|
||||
"session_id": 1,
|
||||
"name": "Session 001 — Louisiane post-apo",
|
||||
"seed_global": 42,
|
||||
"mode": "sandbox"
|
||||
},
|
||||
"tick": {
|
||||
"tick_sleep_sec": 60,
|
||||
"ticks_per_day": 24,
|
||||
"day_start_tick": 6,
|
||||
"night_start_tick": 20,
|
||||
"llm_tick_interval": 6
|
||||
},
|
||||
"difficulty": {
|
||||
"preset": "normal",
|
||||
"survival_drain_food": 1,
|
||||
"survival_drain_water": 1,
|
||||
"survival_drain_sleep": 1,
|
||||
"encounter_rate_multiplier": 1.0,
|
||||
"combat_lethality": 1.0,
|
||||
"resource_scarcity": 1.0
|
||||
},
|
||||
"difficulty_presets": {
|
||||
"easy": { "survival_drain_food": 0, "survival_drain_water": 0, "survival_drain_sleep": 0, "encounter_rate_multiplier": 0.5, "combat_lethality": 0.7 },
|
||||
"normal": { "survival_drain_food": 1, "survival_drain_water": 1, "survival_drain_sleep": 1, "encounter_rate_multiplier": 1.0, "combat_lethality": 1.0 },
|
||||
"hard": { "survival_drain_food": 2, "survival_drain_water": 2, "survival_drain_sleep": 1, "encounter_rate_multiplier": 1.5, "combat_lethality": 1.3 },
|
||||
"nightmare": { "survival_drain_food": 3, "survival_drain_water": 3, "survival_drain_sleep": 2, "encounter_rate_multiplier": 2.0, "combat_lethality": 1.8 }
|
||||
},
|
||||
"llm": {
|
||||
"enabled": false,
|
||||
"model_mj": "qwen2.5:14b",
|
||||
"model_pnj": "qwen2.5:7b",
|
||||
"ollama_url": "http://localhost:11434",
|
||||
"chroma_url": "http://localhost:8000",
|
||||
"chroma_collection": "fallout_vst"
|
||||
},
|
||||
"multi_session": {
|
||||
"allow_parallel": true,
|
||||
"max_sessions": 4,
|
||||
"session_db_host": "localhost",
|
||||
"session_db_port": 15432
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ DB_PASS = os.getenv("DB_PASS", "VeniceOfWasteland2026!")
|
||||
TICKS_PER_DAY = 24 # 1 tick = 1 heure in-game
|
||||
DAY_START_TICK = 6 # 06h00
|
||||
NIGHT_START_TICK = 20 # 20h00
|
||||
TICK_SLEEP_SEC = 0 # 0 = bot-speed (pré-sim), sinon secondes entre ticks
|
||||
TICK_SLEEP_SEC = float(os.getenv("TICK_SLEEP_SEC", 0)) # 0=bot-speed, 60=1tick/min
|
||||
|
||||
# --- Session ---
|
||||
SESSION_ID = int(os.getenv("SESSION_ID", 1))
|
||||
|
||||
Reference in New Issue
Block a user