@minicode-dev/chat 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +25 -0
  2. package/assets/sounds/tanks/big_explosion.mp3 +0 -0
  3. package/assets/sounds/tanks/big_explosion.ogg +0 -0
  4. package/assets/sounds/tanks/cannon_fire.mp3 +0 -0
  5. package/assets/sounds/tanks/cannon_fire.ogg +0 -0
  6. package/assets/sounds/tanks/defeat.mp3 +0 -0
  7. package/assets/sounds/tanks/defeat.ogg +0 -0
  8. package/assets/sounds/tanks/engine_loop.mp3 +0 -0
  9. package/assets/sounds/tanks/engine_loop.ogg +0 -0
  10. package/assets/sounds/tanks/engine_start.mp3 +0 -0
  11. package/assets/sounds/tanks/engine_start.ogg +0 -0
  12. package/assets/sounds/tanks/projectile_whistle.mp3 +0 -0
  13. package/assets/sounds/tanks/projectile_whistle.ogg +0 -0
  14. package/assets/sounds/tanks/tank_destroyed.mp3 +0 -0
  15. package/assets/sounds/tanks/tank_destroyed.ogg +0 -0
  16. package/assets/sounds/tanks/tank_hit.mp3 +0 -0
  17. package/assets/sounds/tanks/tank_hit.ogg +0 -0
  18. package/assets/sounds/tanks/terrain_impact.mp3 +0 -0
  19. package/assets/sounds/tanks/terrain_impact.ogg +0 -0
  20. package/assets/sounds/tanks/tracks_loop.mp3 +0 -0
  21. package/assets/sounds/tanks/tracks_loop.ogg +0 -0
  22. package/assets/sounds/tanks/turret_loop.mp3 +0 -0
  23. package/assets/sounds/tanks/turret_loop.ogg +0 -0
  24. package/assets/sounds/tanks/victory.mp3 +0 -0
  25. package/assets/sounds/tanks/victory.ogg +0 -0
  26. package/dist/chunks/{BattleshipGameCard-S-UdM6rn.js → BattleshipGameCard-BFqA0cjT.js} +67 -65
  27. package/dist/chunks/{BattleshipGameCard-S-UdM6rn.js.map → BattleshipGameCard-BFqA0cjT.js.map} +1 -1
  28. package/dist/chunks/{BattleshipPrototype-Dm6U_mCr.js → BattleshipPrototype-CmQd5AUO.js} +4 -3
  29. package/dist/chunks/{BattleshipPrototype-Dm6U_mCr.js.map → BattleshipPrototype-CmQd5AUO.js.map} +1 -1
  30. package/dist/chunks/ChatContext-dty_KPDX.js +90 -0
  31. package/dist/chunks/ChatContext-dty_KPDX.js.map +1 -0
  32. package/dist/chunks/TanksGameCard-D0n-lzTs.js +1535 -0
  33. package/dist/chunks/TanksGameCard-D0n-lzTs.js.map +1 -0
  34. package/dist/chunks/TanksLiveGameCard-BmNRXP66.js +849 -0
  35. package/dist/chunks/TanksLiveGameCard-BmNRXP66.js.map +1 -0
  36. package/dist/chunks/WebRtcLab-B7xBlJy1.js +510 -0
  37. package/dist/chunks/WebRtcLab-B7xBlJy1.js.map +1 -0
  38. package/dist/chunks/{battleship-DXKuIZ1I.js → board-DXKuIZ1I.js} +1 -1
  39. package/dist/chunks/board-DXKuIZ1I.js.map +1 -0
  40. package/dist/chunks/connect4Sounds-BN7d_TM2.js +3235 -0
  41. package/dist/chunks/connect4Sounds-BN7d_TM2.js.map +1 -0
  42. package/dist/chunks/core-geo-UJY-tH9L.js +361 -0
  43. package/dist/chunks/core-geo-UJY-tH9L.js.map +1 -0
  44. package/dist/chunks/createBattleScene-L4ZRWDED.js +1265 -0
  45. package/dist/chunks/createBattleScene-L4ZRWDED.js.map +1 -0
  46. package/dist/chunks/createLiveScene-CtYPcOPo.js +364 -0
  47. package/dist/chunks/createLiveScene-CtYPcOPo.js.map +1 -0
  48. package/dist/chunks/createPeerLink-qei05vpV.js +478 -0
  49. package/dist/chunks/createPeerLink-qei05vpV.js.map +1 -0
  50. package/dist/chunks/createTankScene-CV7Brxmt.js +685 -0
  51. package/dist/chunks/createTankScene-CV7Brxmt.js.map +1 -0
  52. package/dist/chunks/fx-BHsl4S_J.js +1470 -0
  53. package/dist/chunks/fx-BHsl4S_J.js.map +1 -0
  54. package/dist/chunks/gameStatus-Cu1Z8un9.js +69 -0
  55. package/dist/chunks/gameStatus-Cu1Z8un9.js.map +1 -0
  56. package/dist/chunks/signalFaults-Dv2YjjKy.js +36 -0
  57. package/dist/chunks/signalFaults-Dv2YjjKy.js.map +1 -0
  58. package/dist/chunks/tanksSounds-C99OD2xD.js +177 -0
  59. package/dist/chunks/tanksSounds-C99OD2xD.js.map +1 -0
  60. package/dist/index.js +2404 -2427
  61. package/dist/index.js.map +1 -1
  62. package/dist/styles.css +1 -1
  63. package/index.d.ts +63 -0
  64. package/package.json +1 -1
  65. package/dist/chunks/battleship-DXKuIZ1I.js.map +0 -1
  66. package/dist/chunks/connect4Sounds-9u0p-dAf.js +0 -1847
  67. package/dist/chunks/connect4Sounds-9u0p-dAf.js.map +0 -1
  68. package/dist/chunks/createBattleScene-HIAvjixn.js +0 -1621
  69. package/dist/chunks/createBattleScene-HIAvjixn.js.map +0 -1
@@ -0,0 +1,510 @@
1
+ import { n as e, t } from "./createPeerLink-qei05vpV.js";
2
+ import { t as n } from "./signalFaults-Dv2YjjKy.js";
3
+ import { useEffect as r, useRef as i, useState as a } from "react";
4
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
5
+ // src/net/webrtc/WebRtcLab.jsx
6
+ var c = {
7
+ connected: "ok",
8
+ listening: "warn",
9
+ calling: "warn",
10
+ negotiating: "warn",
11
+ restarting: "warn",
12
+ reconnecting: "warn",
13
+ failed: "bad"
14
+ }, l = [
15
+ 10,
16
+ 15,
17
+ 20,
18
+ 30,
19
+ 60
20
+ ], u = "\n.lira-rtc-lab { --bg:#0d1117; --panel:#161b22; --line:#30363d; --text:#e6edf3; --muted:#8b949e;\n color-scheme: dark; background: var(--bg); color: var(--text); font: 13px/1.45 system-ui, sans-serif; padding: 12px 16px 32px; border-radius: 12px; }\n.lira-rtc-lab h1 { font-size: 18px; margin: 4px 0 2px; }\n.lira-rtc-lab .sub { color: var(--muted); margin: 0 0 12px; }\n.lira-rtc-lab .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; }\n.lira-rtc-lab section { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; min-width: 0; }\n.lira-rtc-lab section h2 { font-size: 12px; margin: 0 0 8px; color: #c9d1d9; text-transform: uppercase; letter-spacing: .05em; }\n.lira-rtc-lab label { display: flex; gap: 6px; align-items: center; margin: 4px 0; flex-wrap: wrap; }\n.lira-rtc-lab label > span { min-width: 92px; color: var(--muted); }\n.lira-rtc-lab input, .lira-rtc-lab select { background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: 6px; padding: 4px 6px; font: inherit; min-width: 0; }\n.lira-rtc-lab input[type=text], .lira-rtc-lab input[type=password] { flex: 1; }\n.lira-rtc-lab input[type=number] { width: 80px; }\n.lira-rtc-lab button { background: #238636; color: #fff; border: 0; border-radius: 6px; padding: 6px 10px; font: inherit; font-weight: 600; cursor: pointer; }\n.lira-rtc-lab button.secondary { background: var(--line); }\n.lira-rtc-lab button.danger { background: #b62324; }\n.lira-rtc-lab button:disabled { opacity: .45; cursor: not-allowed; }\n.lira-rtc-lab .row { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; align-items: center; }\n.lira-rtc-lab dl { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 0; }\n.lira-rtc-lab dt { color: var(--muted); }\n.lira-rtc-lab dd { margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }\n.lira-rtc-lab .pill { display: inline-block; padding: 1px 8px; border-radius: 999px; background: var(--line); font-weight: 600; }\n.lira-rtc-lab .ok { background: #1f6f3a; } .lira-rtc-lab .warn { background: #8a5a00; } .lira-rtc-lab .bad { background: #8e1f1f; }\n.lira-rtc-lab canvas { width: 100%; height: 220px; background: #0b1020; border-radius: 8px; display: block; }\n.lira-rtc-lab .log { height: 200px; overflow: auto; background: var(--bg); border-radius: 6px; padding: 6px; font: 11px ui-monospace, monospace; white-space: pre-wrap; }\n.lira-rtc-lab .wide { grid-column: 1 / -1; }\n";
21
+ function d(e) {
22
+ let t = [], n = e.stun.split(/[\s,]+/).filter(Boolean);
23
+ return n.length && t.push({ urls: n }), e.turnUrl.trim() && t.push({
24
+ urls: e.turnUrl.split(/[\s,]+/).filter(Boolean),
25
+ username: e.turnUser,
26
+ credential: e.turnPass
27
+ }), t;
28
+ }
29
+ function f({ selfId: f, selfName: p, peers: m = [], defaultPeerId: h = "", createSignaling: g, socketStatus: _ = "—", disabledReason: v = "", title: y = "WebRTC Lab · TANQUES LIVE", children: b = null, iceServers: x = t }) {
30
+ let [S, C] = a({
31
+ peerId: h,
32
+ stun: [].concat(x.find((e) => !e.username)?.urls ?? []).join(" "),
33
+ turnUrl: "",
34
+ turnUser: "",
35
+ turnPass: "",
36
+ relayOnly: !1,
37
+ drop: 0,
38
+ duplicate: 0,
39
+ jitter: 0,
40
+ hz: 20
41
+ }), [w, T] = a(null), [E, D] = a(!1), [O, k] = a(!1), [A, j] = a(!1), [M, N] = a([]), [P, F] = a({
42
+ expected: 0,
43
+ received: 0,
44
+ orderErrors: 0,
45
+ last: 0,
46
+ ms: null,
47
+ sentAt: 0
48
+ }), I = i(null), L = i(null), R = i(S), z = i(null), B = i(null), V = i(null), H = i(P);
49
+ R.current = S, H.current = P, r(() => {
50
+ !S.peerId && h && C((e) => ({
51
+ ...e,
52
+ peerId: h
53
+ }));
54
+ }, [h, S.peerId]);
55
+ let U = (e) => N((t) => [...t.slice(-199), `${(/* @__PURE__ */ new Date()).toISOString().slice(11, 23)} ${e}`]), W = (e) => (t) => {
56
+ let n = t.target.type === "checkbox" ? t.target.checked : t.target.value;
57
+ C((t) => ({
58
+ ...t,
59
+ [e]: [
60
+ "drop",
61
+ "duplicate",
62
+ "jitter",
63
+ "hz"
64
+ ].includes(e) ? Number(n) : n
65
+ }));
66
+ }, G = () => {
67
+ I.current?.destroy(), L.current?.dispose(), I.current = null, L.current = null, z.current = null, D(!1), j(!1), T(null), U("laboratorio detenido y recursos liberados");
68
+ }, K = async () => {
69
+ let t = R.current, r = String(t.peerId ?? "").trim();
70
+ if (!f || !r) U("faltan ids de usuario");
71
+ else if (String(r) === String(f)) U("el rival debe ser otro usuario");
72
+ else {
73
+ k(!0);
74
+ try {
75
+ let i = await g({
76
+ peerId: r,
77
+ log: U
78
+ }), a = n(i, () => R.current);
79
+ L.current = a, I.current = e({
80
+ signaling: a,
81
+ selfId: f,
82
+ peerId: r,
83
+ iceServers: d(t),
84
+ iceTransportPolicy: t.relayOnly ? "relay" : "all",
85
+ onChange: T,
86
+ onMove: (e) => {
87
+ z.current = e;
88
+ },
89
+ onEvent: (e) => {
90
+ e?.resetBurst ? F({
91
+ expected: 0,
92
+ received: 0,
93
+ orderErrors: 0,
94
+ last: 0,
95
+ ms: null,
96
+ sentAt: performance.now()
97
+ }) : e?.burst == null ? U(`evento: ${JSON.stringify(e).slice(0, 120)}`) : F((t) => {
98
+ let n = t.received + 1;
99
+ return {
100
+ ...t,
101
+ received: n,
102
+ last: e.burst,
103
+ expected: e.total,
104
+ orderErrors: t.orderErrors + (e.burst === t.last + 1 ? 0 : 1),
105
+ ms: n === e.total ? Math.round(performance.now() - t.sentAt) : t.ms
106
+ };
107
+ });
108
+ },
109
+ log: U
110
+ }), D(!0), U(`laboratorio iniciado: yo ${p ?? f} · rival ${r}`);
111
+ } catch (e) {
112
+ U(`no se pudo iniciar la señalización: ${e?.message ?? e}`);
113
+ } finally {
114
+ k(!1);
115
+ }
116
+ }
117
+ };
118
+ r(() => () => {
119
+ I.current?.destroy(), L.current?.dispose();
120
+ }, []), r(() => {
121
+ let e = () => I.current?.destroy();
122
+ return window.addEventListener("pagehide", e), () => window.removeEventListener("pagehide", e);
123
+ }, []), r(() => () => {
124
+ I.current && G();
125
+ }, [f]), r(() => {
126
+ if (!A) return;
127
+ let e = performance.now(), t = setInterval(() => {
128
+ let t = (performance.now() - e) / 1e3, n = {
129
+ x: .5 + .35 * Math.cos(t * .9),
130
+ y: .5 + .3 * Math.sin(t * 1.3),
131
+ a: t
132
+ };
133
+ B.current = n, I.current?.sendMove({
134
+ x: Math.round(n.x * 1e4) / 1e4,
135
+ y: Math.round(n.y * 1e4) / 1e4,
136
+ a: Math.round(n.a * 100) / 100
137
+ });
138
+ }, 1e3 / S.hz);
139
+ return () => clearInterval(t);
140
+ }, [A, S.hz]), r(() => {
141
+ let e = 0, t = () => {
142
+ e = requestAnimationFrame(t);
143
+ let n = V.current;
144
+ if (!n) return;
145
+ let { clientWidth: r, clientHeight: i } = n, a = window.devicePixelRatio || 1;
146
+ n.width !== Math.round(r * a) && (n.width = Math.round(r * a), n.height = Math.round(i * a));
147
+ let o = n.getContext("2d");
148
+ o.setTransform(a, 0, 0, a, 0, 0), o.clearRect(0, 0, r, i);
149
+ let s = (e, t, n) => {
150
+ e && (o.fillStyle = t, o.beginPath(), o.arc(e.x * r, e.y * i, 9, 0, Math.PI * 2), o.fill(), o.fillStyle = "#e6edf3", o.fillText(n, e.x * r + 12, e.y * i + 4));
151
+ };
152
+ s(B.current, "#22c55e", "yo"), s(z.current, "#ef4444", "rival");
153
+ };
154
+ return t(), () => cancelAnimationFrame(e);
155
+ }, []);
156
+ let q = () => {
157
+ F({
158
+ expected: 200,
159
+ received: 0,
160
+ orderErrors: 0,
161
+ last: 0,
162
+ ms: null,
163
+ sentAt: performance.now()
164
+ }), I.current?.sendEvent({ resetBurst: !0 });
165
+ for (let e = 1; e <= 200; e += 1) I.current?.sendEvent({
166
+ burst: e,
167
+ total: 200
168
+ });
169
+ U("ráfaga de 200 eventos fiables enviada");
170
+ };
171
+ r(() => {
172
+ window.__rtcLab = {
173
+ getState: () => I.current?.getState() ?? null,
174
+ burst: () => H.current
175
+ };
176
+ });
177
+ let J = w, Y = J?.phase ?? "idle", X = m.find((e) => String(e.id) === String(S.peerId))?.name ?? S.peerId;
178
+ return /* @__PURE__ */ s("div", {
179
+ className: "lira-rtc-lab",
180
+ children: [
181
+ /* @__PURE__ */ o("style", { children: u }),
182
+ /* @__PURE__ */ o("h1", { children: y }),
183
+ /* @__PURE__ */ s("p", {
184
+ className: "sub",
185
+ children: [
186
+ "Señalización por el chat (",
187
+ /* @__PURE__ */ o("code", { children: "LIRA_WEBRTC_V1:" }),
188
+ " en ",
189
+ /* @__PURE__ */ o("code", { children: "CHAT_MESSAGE" }),
190
+ "), datos por DataChannel."
191
+ ]
192
+ }),
193
+ v ? /* @__PURE__ */ o("p", {
194
+ className: "sub",
195
+ children: /* @__PURE__ */ o("span", {
196
+ className: "pill bad",
197
+ children: v
198
+ })
199
+ }) : null,
200
+ /* @__PURE__ */ s("div", {
201
+ className: "grid",
202
+ children: [
203
+ /* @__PURE__ */ s("section", { children: [
204
+ /* @__PURE__ */ o("h2", { children: "Configuración" }),
205
+ b,
206
+ /* @__PURE__ */ s("label", { children: [
207
+ /* @__PURE__ */ o("span", { children: "Yo" }),
208
+ /* @__PURE__ */ o("strong", { children: p ?? f ?? "—" }),
209
+ p && f ? /* @__PURE__ */ s("small", { children: [
210
+ " (",
211
+ f,
212
+ ")"
213
+ ] }) : null
214
+ ] }),
215
+ /* @__PURE__ */ s("label", { children: [
216
+ /* @__PURE__ */ o("span", { children: "Rival" }),
217
+ m.length ? /* @__PURE__ */ s("select", {
218
+ value: S.peerId,
219
+ onChange: W("peerId"),
220
+ disabled: E,
221
+ children: [/* @__PURE__ */ o("option", {
222
+ value: "",
223
+ children: "— elige —"
224
+ }), m.filter((e) => String(e.id) !== String(f)).map((e) => /* @__PURE__ */ o("option", {
225
+ value: e.id,
226
+ children: e.name ?? e.id
227
+ }, e.id))]
228
+ }) : null,
229
+ /* @__PURE__ */ o("input", {
230
+ type: "text",
231
+ value: S.peerId,
232
+ onChange: W("peerId"),
233
+ placeholder: "user_id del rival",
234
+ disabled: E
235
+ })
236
+ ] }),
237
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "STUN" }), /* @__PURE__ */ o("input", {
238
+ type: "text",
239
+ value: S.stun,
240
+ onChange: W("stun"),
241
+ disabled: E
242
+ })] }),
243
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "TURN" }), /* @__PURE__ */ o("input", {
244
+ type: "text",
245
+ value: S.turnUrl,
246
+ onChange: W("turnUrl"),
247
+ placeholder: "turn:host:3478 (opcional)",
248
+ disabled: E
249
+ })] }),
250
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "TURN usuario" }), /* @__PURE__ */ o("input", {
251
+ type: "text",
252
+ value: S.turnUser,
253
+ onChange: W("turnUser"),
254
+ autoComplete: "off",
255
+ disabled: E
256
+ })] }),
257
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "TURN clave" }), /* @__PURE__ */ o("input", {
258
+ type: "password",
259
+ value: S.turnPass,
260
+ onChange: W("turnPass"),
261
+ autoComplete: "off",
262
+ disabled: E
263
+ })] }),
264
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("input", {
265
+ type: "checkbox",
266
+ checked: S.relayOnly,
267
+ onChange: W("relayOnly"),
268
+ disabled: E
269
+ }), " Forzar solo TURN (relay)"] }),
270
+ /* @__PURE__ */ o("div", {
271
+ className: "row",
272
+ children: E ? /* @__PURE__ */ o("button", {
273
+ type: "button",
274
+ className: "danger",
275
+ onClick: G,
276
+ children: "Detener y limpiar"
277
+ }) : /* @__PURE__ */ o("button", {
278
+ type: "button",
279
+ onClick: K,
280
+ disabled: O || !!v,
281
+ children: O ? "Iniciando…" : "Iniciar laboratorio"
282
+ })
283
+ })
284
+ ] }),
285
+ /* @__PURE__ */ s("section", { children: [
286
+ /* @__PURE__ */ o("h2", { children: "Conexión" }),
287
+ /* @__PURE__ */ s("div", {
288
+ className: "row",
289
+ children: [
290
+ /* @__PURE__ */ s("button", {
291
+ type: "button",
292
+ onClick: () => I.current?.connect(),
293
+ disabled: !E,
294
+ children: ["Conectar con ", X || "rival"]
295
+ }),
296
+ /* @__PURE__ */ o("button", {
297
+ type: "button",
298
+ className: "secondary",
299
+ onClick: () => I.current?.hangup(),
300
+ disabled: !E,
301
+ children: "Colgar"
302
+ }),
303
+ /* @__PURE__ */ o("button", {
304
+ type: "button",
305
+ className: "secondary",
306
+ onClick: () => I.current?.restartIce(),
307
+ disabled: !E || J?.role !== "caller",
308
+ children: "Reiniciar ICE"
309
+ }),
310
+ /* @__PURE__ */ o("button", {
311
+ type: "button",
312
+ className: "secondary",
313
+ onClick: () => {
314
+ L.current?.pause(8e3), U("señalización cortada 8 s");
315
+ },
316
+ disabled: !E,
317
+ children: "Cortar señalización 8 s"
318
+ })
319
+ ]
320
+ }),
321
+ /* @__PURE__ */ s("dl", { children: [
322
+ /* @__PURE__ */ o("dt", { children: "Fase" }),
323
+ /* @__PURE__ */ s("dd", { children: [/* @__PURE__ */ o("span", {
324
+ className: `pill ${c[Y] ?? ""}`,
325
+ "data-testid": "phase",
326
+ children: Y
327
+ }), J?.error ? ` · ${J.error}` : ""] }),
328
+ /* @__PURE__ */ o("dt", { children: "WebSocket" }),
329
+ /* @__PURE__ */ o("dd", { children: _ }),
330
+ /* @__PURE__ */ o("dt", { children: "Ruta" }),
331
+ /* @__PURE__ */ s("dd", {
332
+ "data-testid": "route",
333
+ children: [J?.route ? /* @__PURE__ */ o("span", {
334
+ className: `pill ${J.route.relay ? "warn" : "ok"}`,
335
+ children: J.route.label
336
+ }) : "—", J?.route ? ` · ${J.route.local}/${J.route.remote} · ${J.route.protocol}` : ""]
337
+ }),
338
+ /* @__PURE__ */ o("dt", { children: "Rol" }),
339
+ /* @__PURE__ */ s("dd", { children: [
340
+ J?.role ?? "—",
341
+ " · sesión ",
342
+ J?.sid?.slice(0, 8) ?? "—",
343
+ " · gen ",
344
+ J?.gen ?? 0
345
+ ] }),
346
+ /* @__PURE__ */ o("dt", { children: "ICE" }),
347
+ /* @__PURE__ */ s("dd", { children: [
348
+ J?.ice ?? "—",
349
+ " · conexión ",
350
+ J?.connection ?? "—",
351
+ " · gathering ",
352
+ J?.gathering ?? "—"
353
+ ] }),
354
+ /* @__PURE__ */ o("dt", { children: "Signaling" }),
355
+ /* @__PURE__ */ o("dd", { children: J?.signalingState ?? "—" }),
356
+ /* @__PURE__ */ o("dt", { children: "Reintentos" }),
357
+ /* @__PURE__ */ s("dd", { children: [
358
+ "reconexiones ",
359
+ J?.reconnects ?? 0,
360
+ " · ICE restarts ",
361
+ J?.restarts ?? 0
362
+ ] })
363
+ ] })
364
+ ] }),
365
+ /* @__PURE__ */ s("section", { children: [
366
+ /* @__PURE__ */ o("h2", { children: "Métricas" }),
367
+ /* @__PURE__ */ s("dl", { children: [
368
+ /* @__PURE__ */ o("dt", { children: "RTT (ping)" }),
369
+ /* @__PURE__ */ o("dd", {
370
+ "data-testid": "rtt",
371
+ children: J?.rtt == null ? "—" : `${J.rtt} ms`
372
+ }),
373
+ /* @__PURE__ */ o("dt", { children: "RTT (ICE)" }),
374
+ /* @__PURE__ */ o("dd", { children: J?.statsRtt == null ? "—" : `${J.statsRtt} ms` }),
375
+ /* @__PURE__ */ o("dt", { children: "Movimiento" }),
376
+ /* @__PURE__ */ s("dd", { children: [
377
+ "salida ",
378
+ J?.move.hzOut ?? 0,
379
+ " Hz · entrada ",
380
+ J?.move.hzIn ?? 0,
381
+ " Hz"
382
+ ] }),
383
+ /* @__PURE__ */ o("dt", { children: "Paquetes" }),
384
+ /* @__PURE__ */ s("dd", {
385
+ "data-testid": "loss",
386
+ children: [
387
+ "enviados ",
388
+ J?.move.sent ?? 0,
389
+ " · recibidos ",
390
+ J?.move.received ?? 0,
391
+ " · perdidos ",
392
+ J?.move.lossPct ?? 0,
393
+ " % · desordenados ",
394
+ J?.move.outOfOrder ?? 0,
395
+ " · descartados ",
396
+ J?.move.dropped ?? 0
397
+ ]
398
+ }),
399
+ /* @__PURE__ */ o("dt", { children: "Eventos" }),
400
+ /* @__PURE__ */ s("dd", { children: [
401
+ "enviados ",
402
+ J?.events.sent ?? 0,
403
+ " · recibidos ",
404
+ J?.events.received ?? 0
405
+ ] }),
406
+ /* @__PURE__ */ o("dt", { children: "Ráfaga" }),
407
+ /* @__PURE__ */ s("dd", {
408
+ "data-testid": "burst",
409
+ children: [
410
+ P.received,
411
+ "/",
412
+ P.expected,
413
+ " · errores de orden ",
414
+ P.orderErrors,
415
+ P.ms == null ? "" : ` · ${P.ms} ms`
416
+ ]
417
+ }),
418
+ /* @__PURE__ */ o("dt", { children: "Señales" }),
419
+ /* @__PURE__ */ s("dd", { children: [
420
+ "env ",
421
+ J?.signals.sent ?? 0,
422
+ " · rec ",
423
+ J?.signals.received ?? 0,
424
+ " · retrans ",
425
+ J?.signals.retransmits ?? 0,
426
+ " · dup ",
427
+ J?.signals.duplicates ?? 0,
428
+ " · ignor ",
429
+ J?.signals.ignored ?? 0,
430
+ " · inválidas ",
431
+ J?.signals.invalid ?? 0,
432
+ " · máx ",
433
+ J?.signals.maxLength ?? 0,
434
+ " chars"
435
+ ] })
436
+ ] }),
437
+ /* @__PURE__ */ s("div", {
438
+ className: "row",
439
+ children: [
440
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "Frecuencia" }), /* @__PURE__ */ o("select", {
441
+ value: S.hz,
442
+ onChange: W("hz"),
443
+ children: l.map((e) => /* @__PURE__ */ s("option", {
444
+ value: e,
445
+ children: [e, " Hz"]
446
+ }, e))
447
+ })] }),
448
+ /* @__PURE__ */ o("button", {
449
+ type: "button",
450
+ onClick: () => j((e) => !e),
451
+ disabled: Y !== "connected" && !A,
452
+ children: A ? "Parar posiciones" : "Enviar posiciones"
453
+ }),
454
+ /* @__PURE__ */ o("button", {
455
+ type: "button",
456
+ className: "secondary",
457
+ onClick: q,
458
+ disabled: Y !== "connected",
459
+ children: "Ráfaga 200 eventos"
460
+ })
461
+ ]
462
+ })
463
+ ] }),
464
+ /* @__PURE__ */ s("section", { children: [
465
+ /* @__PURE__ */ o("h2", { children: "Fallos de señalización (recepción)" }),
466
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "Pérdida %" }), /* @__PURE__ */ o("input", {
467
+ type: "number",
468
+ min: "0",
469
+ max: "90",
470
+ value: S.drop,
471
+ onChange: W("drop")
472
+ })] }),
473
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "Duplicados %" }), /* @__PURE__ */ o("input", {
474
+ type: "number",
475
+ min: "0",
476
+ max: "100",
477
+ value: S.duplicate,
478
+ onChange: W("duplicate")
479
+ })] }),
480
+ /* @__PURE__ */ s("label", { children: [/* @__PURE__ */ o("span", { children: "Jitter ms" }), /* @__PURE__ */ o("input", {
481
+ type: "number",
482
+ min: "0",
483
+ max: "5000",
484
+ value: S.jitter,
485
+ onChange: W("jitter")
486
+ })] }),
487
+ /* @__PURE__ */ o("p", {
488
+ className: "sub",
489
+ children: "El jitter desordena los mensajes. Solo afectan a las señales, no al DataChannel."
490
+ })
491
+ ] }),
492
+ /* @__PURE__ */ s("section", {
493
+ className: "wide",
494
+ children: [/* @__PURE__ */ o("h2", { children: "Simulación" }), /* @__PURE__ */ o("canvas", { ref: V })]
495
+ }),
496
+ /* @__PURE__ */ s("section", {
497
+ className: "wide",
498
+ children: [/* @__PURE__ */ o("h2", { children: "Registro" }), /* @__PURE__ */ o("div", {
499
+ className: "log",
500
+ children: M.join("\n")
501
+ })]
502
+ })
503
+ ]
504
+ })
505
+ ]
506
+ });
507
+ }
508
+ export { f as default };
509
+
510
+ //# sourceMappingURL=WebRtcLab-B7xBlJy1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebRtcLab-B7xBlJy1.js","names":["createPeerLink","withSignalFaults","PHASE_CLASS","HZ","STYLES","iceServersFrom","servers","stun","form","WebRtcLab","DEFAULT_ICE_SERVERS","setForm","useState","defaultPeerId","iceServers","server","state","setState","running","setRunning","starting","setStarting","sending","setSending","logs","setLogs","burst","setBurst","linkRef","useRef","signalingRef","formRef","remoteRef","localRef","canvasRef","burstRef","useEffect","current","log","items","line","update","value","event","key","stop","start","f","peerId","selfId","base","createSignaling","signaling","payload","received","b","selfName","error","onHide","startedAt","timer","me","frame","draw","canvas","w","h","ratio","ctx","dot","p","color","label","sendBurst","n","s","phase","peerName","peers","peer","title","disabledReason","children","socketStatus","hz","v"],"sources":["../../src/net/webrtc/WebRtcLab.jsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { createPeerLink, DEFAULT_ICE_SERVERS } from './createPeerLink.js';\nimport { withSignalFaults } from './signalFaults.js';\n\n// WebRTC lab (TANQUES LIVE groundwork): two users connect a DataChannel pair while\n// the chat carries only the signaling. Host-agnostic: the host passes who the user\n// is, who the peers are and createSignaling({ peerId }) → { send, subscribe }.\n// Exposes window.__rtcLab for automated checks.\n\nconst PHASE_CLASS = { connected: 'ok', listening: 'warn', calling: 'warn', negotiating: 'warn', restarting: 'warn', reconnecting: 'warn', failed: 'bad' };\nconst HZ = [10, 15, 20, 30, 60];\n\nconst STYLES = `\n.lira-rtc-lab { --bg:#0d1117; --panel:#161b22; --line:#30363d; --text:#e6edf3; --muted:#8b949e;\n color-scheme: dark; background: var(--bg); color: var(--text); font: 13px/1.45 system-ui, sans-serif; padding: 12px 16px 32px; border-radius: 12px; }\n.lira-rtc-lab h1 { font-size: 18px; margin: 4px 0 2px; }\n.lira-rtc-lab .sub { color: var(--muted); margin: 0 0 12px; }\n.lira-rtc-lab .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; }\n.lira-rtc-lab section { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; min-width: 0; }\n.lira-rtc-lab section h2 { font-size: 12px; margin: 0 0 8px; color: #c9d1d9; text-transform: uppercase; letter-spacing: .05em; }\n.lira-rtc-lab label { display: flex; gap: 6px; align-items: center; margin: 4px 0; flex-wrap: wrap; }\n.lira-rtc-lab label > span { min-width: 92px; color: var(--muted); }\n.lira-rtc-lab input, .lira-rtc-lab select { background: var(--bg); color: var(--text); border: 1px solid var(--line); border-radius: 6px; padding: 4px 6px; font: inherit; min-width: 0; }\n.lira-rtc-lab input[type=text], .lira-rtc-lab input[type=password] { flex: 1; }\n.lira-rtc-lab input[type=number] { width: 80px; }\n.lira-rtc-lab button { background: #238636; color: #fff; border: 0; border-radius: 6px; padding: 6px 10px; font: inherit; font-weight: 600; cursor: pointer; }\n.lira-rtc-lab button.secondary { background: var(--line); }\n.lira-rtc-lab button.danger { background: #b62324; }\n.lira-rtc-lab button:disabled { opacity: .45; cursor: not-allowed; }\n.lira-rtc-lab .row { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0; align-items: center; }\n.lira-rtc-lab dl { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 0; }\n.lira-rtc-lab dt { color: var(--muted); }\n.lira-rtc-lab dd { margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }\n.lira-rtc-lab .pill { display: inline-block; padding: 1px 8px; border-radius: 999px; background: var(--line); font-weight: 600; }\n.lira-rtc-lab .ok { background: #1f6f3a; } .lira-rtc-lab .warn { background: #8a5a00; } .lira-rtc-lab .bad { background: #8e1f1f; }\n.lira-rtc-lab canvas { width: 100%; height: 220px; background: #0b1020; border-radius: 8px; display: block; }\n.lira-rtc-lab .log { height: 200px; overflow: auto; background: var(--bg); border-radius: 6px; padding: 6px; font: 11px ui-monospace, monospace; white-space: pre-wrap; }\n.lira-rtc-lab .wide { grid-column: 1 / -1; }\n`;\n\nfunction iceServersFrom(form) {\n const servers = [];\n const stun = form.stun.split(/[\\s,]+/).filter(Boolean);\n if (stun.length) servers.push({ urls: stun });\n if (form.turnUrl.trim()) servers.push({ urls: form.turnUrl.split(/[\\s,]+/).filter(Boolean), username: form.turnUser, credential: form.turnPass });\n return servers;\n}\n\n/**\n * @param {{\n * selfId: string|null, selfName?: string,\n * peers?: { id: string, name?: string }[], defaultPeerId?: string,\n * createSignaling: (opts: { peerId: string, log: (line: string) => void }) => any,\n * socketStatus?: string, disabledReason?: string, title?: string, children?: any,\n * iceServers?: RTCIceServer[],\n * }} props\n */\nexport default function WebRtcLab({\n selfId, selfName, peers = [], defaultPeerId = '', createSignaling, socketStatus = '—',\n disabledReason = '', title = 'WebRTC Lab · TANQUES LIVE', children = null, iceServers = DEFAULT_ICE_SERVERS,\n}) {\n const [form, setForm] = useState({\n peerId: defaultPeerId,\n stun: [].concat(iceServers.find((server) => !server.username)?.urls ?? []).join(' '),\n turnUrl: '', turnUser: '', turnPass: '', relayOnly: false,\n drop: 0, duplicate: 0, jitter: 0, hz: 20,\n });\n const [state, setState] = useState(null);\n const [running, setRunning] = useState(false);\n const [starting, setStarting] = useState(false);\n const [sending, setSending] = useState(false);\n const [logs, setLogs] = useState([]);\n const [burst, setBurst] = useState({ expected: 0, received: 0, orderErrors: 0, last: 0, ms: null, sentAt: 0 });\n const linkRef = useRef(null);\n const signalingRef = useRef(null);\n const formRef = useRef(form);\n const remoteRef = useRef(null);\n const localRef = useRef(null);\n const canvasRef = useRef(null);\n const burstRef = useRef(burst);\n formRef.current = form;\n burstRef.current = burst;\n\n useEffect(() => {\n if (!form.peerId && defaultPeerId) setForm((current) => ({ ...current, peerId: defaultPeerId }));\n }, [defaultPeerId, form.peerId]);\n\n const log = (line) => setLogs((items) => [...items.slice(-199), `${new Date().toISOString().slice(11, 23)} ${line}`]);\n const update = (key) => (event) => {\n const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;\n setForm((current) => ({ ...current, [key]: ['drop', 'duplicate', 'jitter', 'hz'].includes(key) ? Number(value) : value }));\n };\n\n const stop = () => {\n linkRef.current?.destroy();\n signalingRef.current?.dispose();\n linkRef.current = null;\n signalingRef.current = null;\n remoteRef.current = null;\n setRunning(false);\n setSending(false);\n setState(null);\n log('laboratorio detenido y recursos liberados');\n };\n\n const start = async () => {\n const f = formRef.current;\n const peerId = String(f.peerId ?? '').trim();\n if (!selfId || !peerId) { log('faltan ids de usuario'); return; }\n if (String(peerId) === String(selfId)) { log('el rival debe ser otro usuario'); return; }\n setStarting(true);\n try {\n const base = await createSignaling({ peerId, log });\n const signaling = withSignalFaults(base, () => formRef.current);\n signalingRef.current = signaling;\n linkRef.current = createPeerLink({\n signaling, selfId, peerId,\n iceServers: iceServersFrom(f),\n iceTransportPolicy: f.relayOnly ? 'relay' : 'all',\n onChange: setState,\n onMove: (payload) => { remoteRef.current = payload; },\n onEvent: (payload) => {\n if (payload?.resetBurst) { setBurst({ expected: 0, received: 0, orderErrors: 0, last: 0, ms: null, sentAt: performance.now() }); return; }\n if (payload?.burst == null) { log(`evento: ${JSON.stringify(payload).slice(0, 120)}`); return; }\n setBurst((b) => {\n const received = b.received + 1;\n return {\n ...b, received, last: payload.burst, expected: payload.total,\n orderErrors: b.orderErrors + (payload.burst === b.last + 1 ? 0 : 1),\n ms: received === payload.total ? Math.round(performance.now() - b.sentAt) : b.ms,\n };\n });\n },\n log,\n });\n setRunning(true);\n log(`laboratorio iniciado: yo ${selfName ?? selfId} · rival ${peerId}`);\n } catch (error) {\n log(`no se pudo iniciar la señalización: ${error?.message ?? error}`);\n } finally {\n setStarting(false);\n }\n };\n\n useEffect(() => () => { linkRef.current?.destroy(); signalingRef.current?.dispose(); }, []);\n useEffect(() => {\n const onHide = () => linkRef.current?.destroy();\n window.addEventListener('pagehide', onHide);\n return () => window.removeEventListener('pagehide', onHide);\n }, []);\n // A different user (logout/login) must not keep the old link.\n useEffect(() => () => { if (linkRef.current) stop(); }, [selfId]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Simulated tank: moves on a curve and streams its position at form.hz.\n useEffect(() => {\n if (!sending) return undefined;\n const startedAt = performance.now();\n const timer = setInterval(() => {\n const t = (performance.now() - startedAt) / 1000;\n const me = { x: 0.5 + 0.35 * Math.cos(t * 0.9), y: 0.5 + 0.3 * Math.sin(t * 1.3), a: t };\n localRef.current = me;\n linkRef.current?.sendMove({ x: Math.round(me.x * 1e4) / 1e4, y: Math.round(me.y * 1e4) / 1e4, a: Math.round(me.a * 100) / 100 });\n }, 1000 / form.hz);\n return () => clearInterval(timer);\n }, [sending, form.hz]);\n\n useEffect(() => {\n let frame = 0;\n const draw = () => {\n frame = requestAnimationFrame(draw);\n const canvas = canvasRef.current;\n if (!canvas) return;\n const { clientWidth: w, clientHeight: h } = canvas;\n const ratio = window.devicePixelRatio || 1;\n if (canvas.width !== Math.round(w * ratio)) { canvas.width = Math.round(w * ratio); canvas.height = Math.round(h * ratio); }\n const ctx = canvas.getContext('2d');\n ctx.setTransform(ratio, 0, 0, ratio, 0, 0);\n ctx.clearRect(0, 0, w, h);\n const dot = (p, color, label) => {\n if (!p) return;\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.arc(p.x * w, p.y * h, 9, 0, Math.PI * 2);\n ctx.fill();\n ctx.fillStyle = '#e6edf3';\n ctx.fillText(label, p.x * w + 12, p.y * h + 4);\n };\n dot(localRef.current, '#22c55e', 'yo');\n dot(remoteRef.current, '#ef4444', 'rival');\n };\n draw();\n return () => cancelAnimationFrame(frame);\n }, []);\n\n const sendBurst = () => {\n const total = 200;\n setBurst({ expected: total, received: 0, orderErrors: 0, last: 0, ms: null, sentAt: performance.now() });\n linkRef.current?.sendEvent({ resetBurst: true });\n for (let n = 1; n <= total; n += 1) linkRef.current?.sendEvent({ burst: n, total });\n log(`ráfaga de ${total} eventos fiables enviada`);\n };\n\n useEffect(() => {\n window.__rtcLab = { getState: () => linkRef.current?.getState() ?? null, burst: () => burstRef.current };\n });\n\n const s = state;\n const phase = s?.phase ?? 'idle';\n const peerName = peers.find((peer) => String(peer.id) === String(form.peerId))?.name ?? form.peerId;\n return (\n <div className=\"lira-rtc-lab\">\n <style>{STYLES}</style>\n <h1>{title}</h1>\n <p className=\"sub\">Señalización por el chat (<code>LIRA_WEBRTC_V1:</code> en <code>CHAT_MESSAGE</code>), datos por DataChannel.</p>\n {disabledReason ? <p className=\"sub\"><span className=\"pill bad\">{disabledReason}</span></p> : null}\n <div className=\"grid\">\n <section>\n <h2>Configuración</h2>\n {children}\n <label><span>Yo</span><strong>{selfName ?? selfId ?? '—'}</strong>{selfName && selfId ? <small> ({selfId})</small> : null}</label>\n <label><span>Rival</span>\n {peers.length ? (\n <select value={form.peerId} onChange={update('peerId')} disabled={running}>\n <option value=\"\">— elige —</option>\n {peers.filter((peer) => String(peer.id) !== String(selfId)).map((peer) => <option key={peer.id} value={peer.id}>{peer.name ?? peer.id}</option>)}\n </select>\n ) : null}\n <input type=\"text\" value={form.peerId} onChange={update('peerId')} placeholder=\"user_id del rival\" disabled={running} />\n </label>\n <label><span>STUN</span><input type=\"text\" value={form.stun} onChange={update('stun')} disabled={running} /></label>\n <label><span>TURN</span><input type=\"text\" value={form.turnUrl} onChange={update('turnUrl')} placeholder=\"turn:host:3478 (opcional)\" disabled={running} /></label>\n <label><span>TURN usuario</span><input type=\"text\" value={form.turnUser} onChange={update('turnUser')} autoComplete=\"off\" disabled={running} /></label>\n <label><span>TURN clave</span><input type=\"password\" value={form.turnPass} onChange={update('turnPass')} autoComplete=\"off\" disabled={running} /></label>\n <label><input type=\"checkbox\" checked={form.relayOnly} onChange={update('relayOnly')} disabled={running} /> Forzar solo TURN (relay)</label>\n <div className=\"row\">\n {!running\n ? <button type=\"button\" onClick={start} disabled={starting || Boolean(disabledReason)}>{starting ? 'Iniciando…' : 'Iniciar laboratorio'}</button>\n : <button type=\"button\" className=\"danger\" onClick={stop}>Detener y limpiar</button>}\n </div>\n </section>\n\n <section>\n <h2>Conexión</h2>\n <div className=\"row\">\n <button type=\"button\" onClick={() => linkRef.current?.connect()} disabled={!running}>Conectar con {peerName || 'rival'}</button>\n <button type=\"button\" className=\"secondary\" onClick={() => linkRef.current?.hangup()} disabled={!running}>Colgar</button>\n <button type=\"button\" className=\"secondary\" onClick={() => linkRef.current?.restartIce()} disabled={!running || s?.role !== 'caller'}>Reiniciar ICE</button>\n <button type=\"button\" className=\"secondary\" onClick={() => { signalingRef.current?.pause(8000); log('señalización cortada 8 s'); }} disabled={!running}>Cortar señalización 8 s</button>\n </div>\n <dl>\n <dt>Fase</dt><dd><span className={`pill ${PHASE_CLASS[phase] ?? ''}`} data-testid=\"phase\">{phase}</span>{s?.error ? ` · ${s.error}` : ''}</dd>\n <dt>WebSocket</dt><dd>{socketStatus}</dd>\n <dt>Ruta</dt><dd data-testid=\"route\">{s?.route ? <span className={`pill ${s.route.relay ? 'warn' : 'ok'}`}>{s.route.label}</span> : '—'}{s?.route ? ` · ${s.route.local}/${s.route.remote} · ${s.route.protocol}` : ''}</dd>\n <dt>Rol</dt><dd>{s?.role ?? '—'} · sesión {s?.sid?.slice(0, 8) ?? '—'} · gen {s?.gen ?? 0}</dd>\n <dt>ICE</dt><dd>{s?.ice ?? '—'} · conexión {s?.connection ?? '—'} · gathering {s?.gathering ?? '—'}</dd>\n <dt>Signaling</dt><dd>{s?.signalingState ?? '—'}</dd>\n <dt>Reintentos</dt><dd>reconexiones {s?.reconnects ?? 0} · ICE restarts {s?.restarts ?? 0}</dd>\n </dl>\n </section>\n\n <section>\n <h2>Métricas</h2>\n <dl>\n <dt>RTT (ping)</dt><dd data-testid=\"rtt\">{s?.rtt != null ? `${s.rtt} ms` : '—'}</dd>\n <dt>RTT (ICE)</dt><dd>{s?.statsRtt != null ? `${s.statsRtt} ms` : '—'}</dd>\n <dt>Movimiento</dt><dd>salida {s?.move.hzOut ?? 0} Hz · entrada {s?.move.hzIn ?? 0} Hz</dd>\n <dt>Paquetes</dt><dd data-testid=\"loss\">enviados {s?.move.sent ?? 0} · recibidos {s?.move.received ?? 0} · perdidos {s?.move.lossPct ?? 0} % · desordenados {s?.move.outOfOrder ?? 0} · descartados {s?.move.dropped ?? 0}</dd>\n <dt>Eventos</dt><dd>enviados {s?.events.sent ?? 0} · recibidos {s?.events.received ?? 0}</dd>\n <dt>Ráfaga</dt><dd data-testid=\"burst\">{burst.received}/{burst.expected} · errores de orden {burst.orderErrors}{burst.ms != null ? ` · ${burst.ms} ms` : ''}</dd>\n <dt>Señales</dt><dd>env {s?.signals.sent ?? 0} · rec {s?.signals.received ?? 0} · retrans {s?.signals.retransmits ?? 0} · dup {s?.signals.duplicates ?? 0} · ignor {s?.signals.ignored ?? 0} · inválidas {s?.signals.invalid ?? 0} · máx {s?.signals.maxLength ?? 0} chars</dd>\n </dl>\n <div className=\"row\">\n <label><span>Frecuencia</span>\n <select value={form.hz} onChange={update('hz')}>{HZ.map((hz) => <option key={hz} value={hz}>{hz} Hz</option>)}</select>\n </label>\n <button type=\"button\" onClick={() => setSending((v) => !v)} disabled={phase !== 'connected' && !sending}>{sending ? 'Parar posiciones' : 'Enviar posiciones'}</button>\n <button type=\"button\" className=\"secondary\" onClick={sendBurst} disabled={phase !== 'connected'}>Ráfaga 200 eventos</button>\n </div>\n </section>\n\n <section>\n <h2>Fallos de señalización (recepción)</h2>\n <label><span>Pérdida %</span><input type=\"number\" min=\"0\" max=\"90\" value={form.drop} onChange={update('drop')} /></label>\n <label><span>Duplicados %</span><input type=\"number\" min=\"0\" max=\"100\" value={form.duplicate} onChange={update('duplicate')} /></label>\n <label><span>Jitter ms</span><input type=\"number\" min=\"0\" max=\"5000\" value={form.jitter} onChange={update('jitter')} /></label>\n <p className=\"sub\">El jitter desordena los mensajes. Solo afectan a las señales, no al DataChannel.</p>\n </section>\n\n <section className=\"wide\">\n <h2>Simulación</h2>\n <canvas ref={canvasRef} />\n </section>\n\n <section className=\"wide\">\n <h2>Registro</h2>\n <div className=\"log\">{logs.join('\\n')}</div>\n </section>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -121,4 +121,4 @@ function d(e, t) {
121
121
  }
122
122
  export { c as a, s as c, r as i, a as l, i as n, l as o, d as r, o as s, e as t };
123
123
 
124
- //# sourceMappingURL=battleship-DXKuIZ1I.js.map
124
+ //# sourceMappingURL=board-DXKuIZ1I.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-DXKuIZ1I.js","names":["FLEET","matrix","fail","isCell","cell","createBoard","validatePlacement","placement","definition","ship","shipId","board","orientation","cells","row","index","col","placeShip","validation","grid","removeShip","item","shot","isFleetComplete","isShipSunk","isFleetSunk","fireShot","result","shots","nextBoard"],"sources":["../../src/games/battleship/logic/board.js"],"sourcesContent":["/** Pure board rules. Coordinates are zero-based { row, col }. Ships may touch. */\nexport const BOARD_SIZE = 10;\nexport const RULES_VERSION = 1;\nexport const FLEET = Object.freeze([\n Object.freeze({ id: 'carrier', size: 5 }),\n Object.freeze({ id: 'battleship', size: 4 }),\n Object.freeze({ id: 'cruiser', size: 3 }),\n Object.freeze({ id: 'submarine', size: 3 }),\n Object.freeze({ id: 'destroyer', size: 2 }),\n]);\nconst matrix = () => Array.from({ length: BOARD_SIZE }, () => Array(BOARD_SIZE).fill(null));\nconst fail = (reason) => ({ ok: false, reason });\n\nexport function isCell(cell) {\n return cell != null && Number.isInteger(cell.row) && Number.isInteger(cell.col)\n && cell.row >= 0 && cell.row < BOARD_SIZE && cell.col >= 0 && cell.col < BOARD_SIZE;\n}\n\nexport function createBoard() {\n return { grid: matrix(), shots: matrix(), ships: [] };\n}\n\nexport function validatePlacement(board, placement) {\n if (!placement || typeof placement !== 'object') return fail('invalid_placement');\n const { shipId, row, col, orientation } = placement;\n const definition = FLEET.find((ship) => ship.id === shipId);\n if (!definition) return fail('unknown_ship');\n if (board.ships.some((ship) => ship.id === shipId)) return fail('already_placed');\n if (orientation !== 'horizontal' && orientation !== 'vertical') return fail('invalid_orientation');\n if (!isCell({ row, col })) return fail('out_of_bounds');\n const cells = Array.from({ length: definition.size }, (_, index) => ({\n row: row + (orientation === 'vertical' ? index : 0),\n col: col + (orientation === 'horizontal' ? index : 0),\n }));\n if (cells.some((cell) => !isCell(cell))) return fail('out_of_bounds');\n if (cells.some((cell) => board.grid[cell.row][cell.col] !== null)) return fail('overlap');\n return { ok: true, ship: { id: shipId, size: definition.size, orientation, cells } };\n}\n\nexport function placeShip(board, placement) {\n const validation = validatePlacement(board, placement);\n if (!validation.ok) return validation;\n const grid = board.grid.map((row) => row.slice());\n const { ship } = validation;\n for (const cell of ship.cells) grid[cell.row][cell.col] = ship.id;\n return { ok: true, board: { ...board, grid, ships: [...board.ships, ship] }, ship };\n}\n\nexport function removeShip(board, shipId) {\n const ship = board.ships.find((item) => item.id === shipId);\n if (!ship) return fail('unknown_ship');\n if (board.shots.some((row) => row.some((shot) => shot !== null))) return fail('battle_started');\n const grid = board.grid.map((row) => row.slice());\n for (const cell of ship.cells) grid[cell.row][cell.col] = null;\n return { ok: true, board: { ...board, grid, ships: board.ships.filter((item) => item.id !== shipId) } };\n}\n\nexport function isFleetComplete(board) {\n return board.ships.length === FLEET.length && FLEET.every((definition) => {\n const ship = board.ships.find((item) => item.id === definition.id);\n return ship?.size === definition.size && ship.cells.length === definition.size;\n });\n}\n\nexport function isShipSunk(board, shipId) {\n const ship = board.ships.find((item) => item.id === shipId);\n return Boolean(ship && ship.cells.every((cell) => board.shots[cell.row][cell.col] === 'hit'));\n}\n\nexport function isFleetSunk(board) {\n return isFleetComplete(board) && board.ships.every((ship) => isShipSunk(board, ship.id));\n}\n\nexport function fireShot(board, cell) {\n if (!isCell(cell)) return fail('out_of_bounds');\n if (board.shots[cell.row][cell.col] !== null) return fail('repeat_shot');\n if (isFleetSunk(board)) return fail('battle_finished');\n const shipId = board.grid[cell.row][cell.col];\n const result = shipId === null ? 'miss' : 'hit';\n const shots = board.shots.map((row) => row.slice());\n shots[cell.row][cell.col] = result;\n const nextBoard = { ...board, shots };\n return {\n ok: true, board: nextBoard, result, shipId,\n sunk: shipId !== null && isShipSunk(nextBoard, shipId),\n defeated: isFleetSunk(nextBoard),\n };\n}\n"],"mappings":"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"}