PageSourceSearch

https://seleccionados.es/keytwister/js/botDevPanel.js

js seleccionados.es collected 2026-09-24 15:00:10 UTC 11,502 bytes, 295 lines download raw bytes

1// ══════════════════════════════════════════════════════════════
2//  botDevPanel.js — In-browser dev panel for bot simulation
3//
4//  Adds a debug panel (hidden by default) that lets developers:
5//    • Launch N runs with chosen profile/skill/difficulty
6//    • See real-time progress bar
7//    • View aggregated metrics and balance alerts
8//    • Export results as JSON/CSV download
9//
10//  This module imports the simulation engine dynamically
11//  and runs everything client-side with NO server dependency.
12//
13//  Enable via URL param: ?devbot=1  or console: BotDevPanel.show()
14// ══════════════════════════════════════════════════════════════
15
16const BotDevPanel = (() => {
17  let _panel = null;
18  let _running = false;
19  let _worker = null;
20
21  // ── Create/show the panel ──
22  function show() {
23    if (_panel) { _panel.style.display = "flex"; return; }
24    _buildUI();
25    _panel.style.display = "flex";
26  }
27
28  function hide() {
29    if (_panel) _panel.style.display = "none";
30  }
31
32  function toggle() {
33    if (_panel && _panel.style.display !== "none") hide();
34    else show();
35  }
36
37  // ── Build the dev panel UI ──
38  function _buildUI() {
39    _panel = document.createElement("div");
40    _panel.id = "botDevPanel";
41    _panel.innerHTML = `
42      <div class="bdp-header">
43        <span>🤖 Bot Dev Panel</span>
44        <button class="bdp-close" title="Cerrar">✕</button>
45      </div>
46      <div class="bdp-body">
47        <div class="bdp-row">
48          <label>Runs:</label>
49          <input type="number" id="bdpRuns" value="100" min="1" max="10000">
50        </div>
51        <div class="bdp-row">
52          <label>Perfiles:</label>
53          <select id="bdpProfiles" multiple size="4">
54            <option value="muro" selected>🧱 Muro</option>
55            <option value="velocista" selected>⚡ Velocista</option>
56            <option value="mercader" selected>💰 Mercader</option>
57            <option value="pianista" selected>🎹 Pianista</option>
58            <option value="novato">🐣 Novato</option>
59            <option value="veterano">🏆 Veterano</option>
60            <option value="dios">👑 Dios</option>
61            <option value="aleatorio">🎲 Aleatorio</option>
62          </select>
63        </div>
64        <div class="bdp-row">
65          <label>Skill override:</label>
66          <select id="bdpSkill">
67            <option value="">— desde perfil —</option>
68            <option value="beginner">Beginner</option>
69            <option value="casual">Casual</option>
70            <option value="average">Average</option>
71            <option value="skilled">Skilled</option>
72            <option value="expert">Expert</option>
73            <option value="godlike">Godlike</option>
74          </select>
75        </div>
76        <div class="bdp-row">
77          <label>Modo:</label>
78          <select id="bdpMode">
79            <option value="party">Party</option>
80            <option value="competitive">Competitive</option>
81          </select>
82        </div>
83        <div class="bdp-row">
84          <label>Dificultad:</label>
85          <select id="bdpDiff">
86            <option value="easy">Easy</option>
87            <option value="normal" selected>Normal</option>
88            <option value="hard">Hard</option>
89            <option value="insane">Insane</option>
90          </select>
91        </div>
92        <div class="bdp-row">
93          <label>Seed:</label>
94          <input type="text" id="bdpSeed" placeholder="random" value="">
95        </div>
96        <div class="bdp-actions">
97          <button id="bdpRunBtn" class="bdp-btn bdp-btn--primary">▶ Ejecutar</button>
98          <button id="bdpStopBtn" class="bdp-btn" disabled>⏹ Parar</button>
99          <button id="bdpExportJson" class="bdp-btn" disabled>💾 JSON</button>
100          <button id="bdpExportCsv" class="bdp-btn" disabled>📄 CSV</button>
101        </div>
102        <div class="bdp-progress" style="display:none">
103          <div class="bdp-progress-bar"><div class="bdp-progress-fill" id="bdpProgressFill"></div></div>
104          <span id="bdpProgressText">0%</span>
105        </div>
106        <div class="bdp-results" id="bdpResults" style="display:none"></div>
107        <div class="bdp-alerts" id="bdpAlerts" style="display:none"></div>
108      </div>
109    `;
110
111    document.body.appendChild(_panel);
112
113    // Events
114    _panel.querySelector(".bdp-close").addEventListener("click", hide);
115    document.getElementById("bdpRunBtn").addEventListener("click", _startSimulation);
116    document.getElementById("bdpStopBtn").addEventListener("click", _stopSimulation);
117    document.getElementById("bdpExportJson").addEventListener("click", _exportJSON);
118    document.getElementById("bdpExportCsv").addEventListener("click", _exportCSV);
119  }
120
121  // ── Run simulation ──
122  let _lastResult = null;
123
124  async function _startSimulation() {
125    if (_running) return;
126    _running = true;
127
128    const runBtn = document.getElementById("bdpRunBtn");
129    const stopBtn = document.getElementById("bdpStopBtn");
130    const exportJsonBtn = document.getElementById("bdpExportJson");
131    const exportCsvBtn = document.getElementById("bdpExportCsv");
132    const progressDiv = _panel.querySelector(".bdp-progress");
133    const resultsDiv = document.getElementById("bdpResults");
134    const alertsDiv = document.getElementById("bdpAlerts");
135
136    runBtn.disabled = true;
137    stopBtn.disabled = false;
138    exportJsonBtn.disabled = true;
139    exportCsvBtn.disabled = true;
140    progressDiv.style.display = "flex";
141    resultsDiv.style.display = "none";
142    alertsDiv.style.display = "none";
143    resultsDiv.innerHTML = "";
144    alertsDiv.innerHTML = "";
145
146    const numRuns = parseInt(document.getElementById("bdpRuns").value, 10) || 100;
147    const profileSel = document.getElementById("bdpProfiles");
148    const profiles = [...profileSel.selectedOptions].map(o => o.value);
149    const skill = document.getElementById("bdpSkill").value || null;
150    const mode = document.getElementById("bdpMode").value;
151    const diff = document.getElementById("bdpDiff").value;
152    const seed = document.getElementById("bdpSeed").value || null;
153
154    try {
155      // Dynamic import of ESM simulation modules
156      const { runBatch } = await import('./bot/botSim.mjs');
157
158      const startMs = performance.now();
159
160      const result = runBatch({
161        runs: numRuns,
162        profiles,
163        skillTiers: skill ? profiles.map(() => skill) : null,
164        mode,
165        difficultyPreset: diff,
166        seed,
167        verbose: false,
168        onProgress: (completed, total) => {
169          const pct = Math.round(completed / total * 100);
170          document.getElementById("bdpProgressFill").style.width = pct + "%";
171          document.getElementById("bdpProgressText").textContent = `${pct}% (${completed}/${total})`;
172        },
173      });
174
175      const elapsed = Math.round(performance.now() - startMs);
176      _lastResult = result;
177
178      // Show results
179      _renderResults(result.aggregated, elapsed, numRuns);
180      _renderAlerts(result.alerts);
181
182      exportJsonBtn.disabled = false;
183      exportCsvBtn.disabled = false;
184    } catch (err) {
185      resultsDiv.style.display = "block";
186      resultsDiv.innerHTML = `<div class="bdp-error">❌ Error: ${err.message}</div>`;
187      console.error("Bot simulation error:", err);
188    }
189
190    _running = false;
191    runBtn.disabled = false;
192    stopBtn.disabled = true;
193  }
194
195  function _stopSimulation() {
196    // For now, can't cancel sync execution — placeholder
197    _running = false;
198  }
199
200  function _renderResults(agg, elapsedMs, numRuns) {
201    const div = document.getElementById("bdpResults");
202    div.style.display = "block";
203
204    const p = agg.progression;
205    const e = agg.economy;
206    const perf = agg.performance;
207
208    let html = `<h4>📊 Resultados (${numRuns} runs, ${elapsedMs}ms)</h4>`;
209    html += `<div class="bdp-stats-grid">`;
210    html += _statCard("Pisos", p.floors.avg, `med=${p.floors.med} σ=${p.floors.std}`);
211    html += _statCard("Score", p.score.avg, `med=${p.score.med} p90=${p.score.p90}`);
212    html += _statCard("Win Rate", (perf.winRate.avg * 100).toFixed(1) + "%", `p10=${(perf.winRate.p10 * 100).toFixed(0)}% p90=${(perf.winRate.p90 * 100).toFixed(0)}%`);
213    html += _statCard("Max Racha", perf.maxStreak.avg, `max=${perf.maxStreak.max}`);
214    html += _statCard("Coins Ganados", e.coinsEarned.avg, `σ=${e.coinsEarned.std}`);
215    html += _statCard("Coins Gastados", e.coinsSpent.avg, ``);
216    html += `</div>`;
217
218    // Floor survival sparkline
219    html += `<h4>📈 Supervivencia por piso</h4><div class="bdp-floor-bars">`;
220    for (const fs of (agg.floorProgression || []).slice(0, 15)) {
221      const h = Math.round(fs.survivalPct * 100);
222      html += `<div class="bdp-floor-bar" title="Piso ${fs.floor}: ${h}% superv, ${(fs.flawlessPct * 100).toFixed(0)}% flawless" style="height:${h}%"><span>${fs.floor}</span></div>`;
223    }
224    html += `</div>`;
225
226    // Top death mutators
227    html += `<h4>💀 Top causas de muerte</h4><div class="bdp-death-list">`;
228    for (const m of (agg.mutators?.deathCauses || []).slice(0, 5)) {
229      html += `<div class="bdp-death-row"><span>${m.id}</span><div class="bdp-death-bar" style="width:${m.pct}%"></div><span>${m.pct}%</span></div>`;
230    }
231    html += `</div>`;
232
233    div.innerHTML = html;
234  }
235
236  function _statCard(label, value, sub) {
237    return `<div class="bdp-stat-card"><div class="bdp-stat-val">${value}</div><div class="bdp-stat-label">${label}</div><div class="bdp-stat-sub">${sub}</div></div>`;
238  }
239
240  function _renderAlerts(alerts) {
241    const div = document.getElementById("bdpAlerts");
242    if (alerts.length === 0) {
243      div.style.display = "block";
244      div.innerHTML = `<div class="bdp-alert bdp-alert--ok">✅ Sin alertas de balance</div>`;
245      return;
246    }
247    div.style.display = "block";
248    let html = `<h4>⚠️ Alertas (${alerts.length})</h4>`;
249    for (const a of alerts) {
250      const cls = a.level === "alert" ? "bdp-alert--danger" : a.level === "warning" ? "bdp-alert--warn" : "bdp-alert--info";
251      html += `<div class="bdp-alert ${cls}">[${a.type}] ${a.message}</div>`;
252    }
253    div.innerHTML = html;
254  }
255
256  // ── Export ──
257  function _exportJSON() {
258    if (!_lastResult) return;
259    _download("twister-bot-results.json", _lastResult.json, "application/json");
260  }
261
262  function _exportCSV() {
263    if (!_lastResult) return;
264    _download("twister-bot-results.csv", _lastResult.csv, "text/csv");
265  }
266
267  function _download(filename, content, mime) {
268    const blob = new Blob([content], { type: mime });
269    const url = URL.createObjectURL(blob);
270    const a = document.createElement("a");
271    a.href = url; a.download = filename;
272    document.body.appendChild(a); a.click();
273    document.body.removeChild(a);
274    URL.revokeObjectURL(url);
275  }
276
277  // ── Auto-init if URL param present ──
278  if (typeof window !== 'undefined') {
279    const params = new URLSearchParams(window.location.search);
280    if (params.get("devbot") === "1") {
281      if (document.readyState === "loading") {
282        document.addEventListener("DOMContentLoaded", show);
283      } else {
284        show();
285      }
286    }
287  }
288
289  return { show, hide, toggle };
290})();
291
292// Expose globally for console access
293if (typeof window !== 'undefined') {
294  window.BotDevPanel = BotDevPanel;
295}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.