@minicode-dev/chat 0.1.15 → 0.1.16

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 (57) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/chunks/{BattleshipGameCard-8H-sBCsv.js → BattleshipGameCard-DwNzgH5G.js} +199 -199
  3. package/dist/chunks/{BattleshipGameCard-8H-sBCsv.js.map → BattleshipGameCard-DwNzgH5G.js.map} +1 -1
  4. package/dist/chunks/{BattleshipPrototype-CmQd5AUO.js → BattleshipPrototype-CSQxQJt5.js} +52 -52
  5. package/dist/chunks/{BattleshipPrototype-CmQd5AUO.js.map → BattleshipPrototype-CSQxQJt5.js.map} +1 -1
  6. package/dist/chunks/{ChatContext-CVW58bzC.js → ChatContext-D5rqvt5D.js} +2 -2
  7. package/dist/chunks/{ChatContext-CVW58bzC.js.map → ChatContext-D5rqvt5D.js.map} +1 -1
  8. package/dist/chunks/RacingLiveGameCard-_wvhDRlG.js +1873 -0
  9. package/dist/chunks/RacingLiveGameCard-_wvhDRlG.js.map +1 -0
  10. package/dist/chunks/{TanksGameCard-DnCufSH6.js → TanksGameCard-B-gtDYYg.js} +137 -137
  11. package/dist/chunks/{TanksGameCard-DnCufSH6.js.map → TanksGameCard-B-gtDYYg.js.map} +1 -1
  12. package/dist/chunks/{TanksLiveGameCard-D_27Jomp.js → TanksLiveGameCard-DXgd1J2Y.js} +392 -392
  13. package/dist/chunks/{TanksLiveGameCard-D_27Jomp.js.map → TanksLiveGameCard-DXgd1J2Y.js.map} +1 -1
  14. package/dist/chunks/{avalancheScene3d-CIE2WKQn.js → avalancheScene3d-CNbQyGUd.js} +2 -2
  15. package/dist/chunks/{avalancheScene3d-CIE2WKQn.js.map → avalancheScene3d-CNbQyGUd.js.map} +1 -1
  16. package/dist/chunks/{connect4Sounds-BqEHULjv.js → connect4Sounds-3a6h7gUC.js} +8 -8
  17. package/dist/chunks/{connect4Sounds-BqEHULjv.js.map → connect4Sounds-3a6h7gUC.js.map} +1 -1
  18. package/dist/chunks/createLiveScene-jrzyQogh.js +756 -0
  19. package/dist/chunks/{createLiveScene-MTGRv8rx.js.map → createLiveScene-jrzyQogh.js.map} +1 -1
  20. package/dist/chunks/createRaceScene-C-aw0dlX.js +291 -0
  21. package/dist/chunks/createRaceScene-C-aw0dlX.js.map +1 -0
  22. package/dist/chunks/{createTankScene-yhX6la27.js → createTankScene-6asWHKFf.js} +123 -125
  23. package/dist/chunks/{createTankScene-yhX6la27.js.map → createTankScene-6asWHKFf.js.map} +1 -1
  24. package/dist/chunks/fx-BNJvJYOu.js +871 -0
  25. package/dist/chunks/fx-BNJvJYOu.js.map +1 -0
  26. package/dist/chunks/{games-Bl_8raG6.js → games-D_9SiSBT.js} +708 -697
  27. package/dist/chunks/games-D_9SiSBT.js.map +1 -0
  28. package/dist/chunks/tank-DIn6Jyt8.js +605 -0
  29. package/dist/chunks/tank-DIn6Jyt8.js.map +1 -0
  30. package/dist/chunks/{tanks-live-BcKmrhnV.js → tanks-live-BRhYW4MG.js} +36 -36
  31. package/dist/chunks/{tanks-live-BcKmrhnV.js.map → tanks-live-BRhYW4MG.js.map} +1 -1
  32. package/dist/chunks/{tanksSounds-DTnidbiq.js → tanksSounds-tYPzje61.js} +4 -4
  33. package/dist/chunks/{tanksSounds-DTnidbiq.js.map → tanksSounds-tYPzje61.js.map} +1 -1
  34. package/dist/chunks/track-C1TWWT19.js +557 -0
  35. package/dist/chunks/track-C1TWWT19.js.map +1 -0
  36. package/dist/index.js +4234 -4018
  37. package/dist/index.js.map +1 -1
  38. package/dist/styles.css +1 -1
  39. package/index.d.ts +2 -0
  40. package/package.json +1 -1
  41. package/dist/chunks/RacingLiveGameCard-8j9QLnxW.js +0 -1654
  42. package/dist/chunks/RacingLiveGameCard-8j9QLnxW.js.map +0 -1
  43. package/dist/chunks/createLiveScene-MTGRv8rx.js +0 -758
  44. package/dist/chunks/createRaceScene-BiEtQsVs.js +0 -306
  45. package/dist/chunks/createRaceScene-BiEtQsVs.js.map +0 -1
  46. package/dist/chunks/decor-CyyCnmct.js +0 -418
  47. package/dist/chunks/decor-CyyCnmct.js.map +0 -1
  48. package/dist/chunks/fx-1ZwzmPWj.js +0 -458
  49. package/dist/chunks/fx-1ZwzmPWj.js.map +0 -1
  50. package/dist/chunks/games-Bl_8raG6.js.map +0 -1
  51. package/dist/chunks/rolldown-runtime-CGnMp29N.js +0 -10
  52. package/dist/chunks/tank-DjEQ4FXF.js +0 -553
  53. package/dist/chunks/tank-DjEQ4FXF.js.map +0 -1
  54. package/dist/chunks/track-BcnnpnAm.js +0 -259
  55. package/dist/chunks/track-BcnnpnAm.js.map +0 -1
  56. package/dist/chunks/util-DwI9bfS7.js +0 -57
  57. package/dist/chunks/util-DwI9bfS7.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tank-DIn6Jyt8.js","names":["GeoBuilder","lin","Color","hex","clamp","v","a","b","clamp01","smooth","t","x","e0","e1","DEG","dampK","rate","dt","wrapPi","hash2","s","y","rng","seed","canvas","c","w","h","tex","CanvasTexture","LinearFilter","repeat","RepeatWrapping","glowTexture","size","g","grd","smokeTexture","rand","i","d","rr","ringTexture","blobTexture","trackTexture","TANK_SCALE","TEAM","W","W2","G","D","M","K","TRACK_Z","TRACK_W","WHEEL_R","WHEEL_X","SPROCKET","IDLER","TRACK_TILE","TURRET_PIVOT","Vector3","GUN_PIVOT","MUZZLE","sec","z","yb","zb","yt","loft","cx","cy","n","r","rings","p","ins","A","B","m","k","k1","a0","a1","b0","b1","isTop","topColor","color","glow","p0","p1","cylX","x0","x1","seg","r0","r1","cylZ","z0","z1","buildHull","buildHullAccent","buildLamps","buildTurret","buildTurretAccent","buildGun","buildWheel","L","zz","buildTrack","pts","arc","lens","P","Nn","UV","side","y0","y1","nx","ny","l","u0","u1","quad","q","BufferGeometry","BufferAttribute","WHEELS","list","createTankGeometries","CHAR","CHAR_ACCENT","createTank","team","root","Group","body","turret","gun","accentMat","MeshBasicMaterial","trackTex","shared","bodyMat","trackMat","hull","Mesh","geos","hullAccent","lamps","track","turretMesh","turretAccent","gunMesh","casters","makeMaterials","MeshLambertMaterial","DoubleSide","MeshStandardMaterial","state","applyCharred","quality","up","nrm","qTilt","Quaternion","qYaw","tiltTarget","tiltInit","place","terrain","setCharred","on","setFlash","setShadows","tmpM","Matrix4","tmpQ","tmpP","tmpS","zAxis","writeWheels","mesh","offset","out","createTankShared"],"sources":["../../src/games/tanks/scene/util.js","../../src/games/tanks/scene/tank.js"],"sourcesContent":["// Shared helpers for the tanks scene (pure, no per-frame allocations).\nimport { CanvasTexture, Color, LinearFilter, RepeatWrapping } from 'three'\n\nexport const lin = (hex) => new Color(hex).convertSRGBToLinear()\nexport const clamp = (v, a, b) => (v < a ? a : v > b ? b : v)\nexport const clamp01 = (v) => (v < 0 ? 0 : v > 1 ? 1 : v)\nexport const lerp = (a, b, t) => a + (b - a) * t\nexport const smooth = (e0, e1, x) => {\n const t = clamp01((x - e0) / (e1 - e0))\n return t * t * (3 - 2 * t)\n}\nexport const DEG = Math.PI / 180\n// exponential damping factor for frame-rate independent smoothing\nexport const dampK = (rate, dt) => 1 - Math.exp(-rate * dt)\nexport const wrapPi = (a) => {\n while (a > Math.PI) a -= Math.PI * 2\n while (a < -Math.PI) a += Math.PI * 2\n return a\n}\n// deterministic hash in [0,1)\nexport const hash2 = (x, y) => {\n const s = Math.sin(x * 127.1 + y * 311.7) * 43758.5453\n return s - Math.floor(s)\n}\n\n// Tiny seeded PRNG for decor jitter (stable across rebuilds).\nexport function rng(seed) {\n let s = seed >>> 0 || 1\n return () => {\n s ^= s << 13\n s ^= s >>> 17\n s ^= s << 5\n return (s >>> 0) / 4294967296\n }\n}\n\nfunction canvas(w, h) {\n const c = document.createElement('canvas')\n c.width = w\n c.height = h\n return c\n}\n\nfunction tex(c, repeat = false) {\n const t = new CanvasTexture(c)\n t.minFilter = LinearFilter\n t.magFilter = LinearFilter\n t.generateMipmaps = false\n if (repeat) {\n t.wrapS = RepeatWrapping\n t.wrapT = RepeatWrapping\n }\n return t\n}\n\n// Soft radial glow (white, alpha falloff) for additive sprites/points.\nexport function glowTexture(size = 64) {\n const c = canvas(size, size)\n const g = c.getContext('2d')\n const r = size / 2\n const grd = g.createRadialGradient(r, r, 0, r, r, r)\n grd.addColorStop(0, 'rgba(255,255,255,1)')\n grd.addColorStop(0.18, 'rgba(255,255,255,0.85)')\n grd.addColorStop(0.45, 'rgba(255,255,255,0.32)')\n grd.addColorStop(1, 'rgba(255,255,255,0)')\n g.fillStyle = grd\n g.fillRect(0, 0, size, size)\n return tex(c)\n}\n\n// Puffy smoke blob: several overlapping soft circles (alpha only).\nexport function smokeTexture(size = 64) {\n const c = canvas(size, size)\n const g = c.getContext('2d')\n const r = size / 2\n const rand = rng(7)\n for (let i = 0; i < 7; i++) {\n const a = rand() * Math.PI * 2\n const d = rand() * r * 0.32\n const x = r + Math.cos(a) * d\n const y = r + Math.sin(a) * d\n const rr = r * (0.45 + rand() * 0.25)\n const grd = g.createRadialGradient(x, y, 0, x, y, rr)\n grd.addColorStop(0, 'rgba(255,255,255,0.55)')\n grd.addColorStop(0.6, 'rgba(255,255,255,0.25)')\n grd.addColorStop(1, 'rgba(255,255,255,0)')\n g.fillStyle = grd\n g.fillRect(0, 0, size, size)\n }\n return tex(c)\n}\n\n// Thin bright ring for shockwaves / target markers.\nexport function ringTexture(size = 128) {\n const c = canvas(size, size)\n const g = c.getContext('2d')\n const r = size / 2\n const grd = g.createRadialGradient(r, r, 0, r, r, r)\n grd.addColorStop(0, 'rgba(255,255,255,0)')\n grd.addColorStop(0.62, 'rgba(255,255,255,0)')\n grd.addColorStop(0.8, 'rgba(255,255,255,0.55)')\n grd.addColorStop(0.9, 'rgba(255,255,255,1)')\n grd.addColorStop(1, 'rgba(255,255,255,0)')\n g.fillStyle = grd\n g.fillRect(0, 0, size, size)\n return tex(c)\n}\n\n// Blob shadow (dark center, soft edge).\nexport function blobTexture(size = 64) {\n const c = canvas(size, size)\n const g = c.getContext('2d')\n const r = size / 2\n const grd = g.createRadialGradient(r, r, 0, r, r, r)\n grd.addColorStop(0, 'rgba(0,0,0,0.6)')\n grd.addColorStop(0.55, 'rgba(0,0,0,0.38)')\n grd.addColorStop(1, 'rgba(0,0,0,0)')\n g.fillStyle = grd\n g.fillRect(0, 0, size, size)\n return tex(c)\n}\n\n// Track tread strip: u runs along the track loop, v across its width.\nexport function trackTexture() {\n const c = canvas(64, 32)\n const g = c.getContext('2d')\n g.fillStyle = '#3b3b3e'\n g.fillRect(0, 0, 64, 32)\n for (let i = 0; i < 4; i++) {\n const x = i * 16\n g.fillStyle = '#1b1b1d'\n g.fillRect(x, 0, 6, 32)\n g.fillStyle = '#5a5a5f'\n g.fillRect(x + 6, 0, 2, 32)\n g.fillStyle = '#2a2a2d'\n g.fillRect(x + 8, 13, 8, 6)\n }\n g.fillStyle = 'rgba(0,0,0,0.35)'\n g.fillRect(0, 0, 64, 3)\n g.fillRect(0, 29, 64, 3)\n return tex(c, true)\n}\n","// Procedural low-poly tank. Geometry is built once and shared by every tank; each tank only\n// owns its materials (team tint, accent glow, track texture offset).\n// Local frame: +x forward, +y up, +z right side. Units: meters before TANK_SCALE.\nimport {\n BufferAttribute,\n BufferGeometry,\n DoubleSide,\n Group,\n Matrix4,\n Mesh,\n MeshBasicMaterial,\n MeshLambertMaterial,\n MeshStandardMaterial,\n Quaternion,\n Vector3,\n} from 'three'\nimport { GeoBuilder } from '../../battleship/scene/core-geo.js'\nimport { lin, trackTexture } from './util.js'\n\nexport const TANK_SCALE = 1.15\nexport const TEAM = {\n green: { tint: lin('#7fa35c'), accent: lin('#22c55e'), hex: '#22c55e' },\n red: { tint: lin('#b8584a'), accent: lin('#ef4444'), hex: '#ef4444' },\n}\nconst W = lin('#e9e6dc') // body panel (tinted by material color)\nconst W2 = lin('#c9c4b6')\nconst G = lin('#6d6a64') // darker panel\nconst D = lin('#2a2927') // grille / dark\nconst M = lin('#55565b') // metal (barrel ends)\nconst K = lin('#ffffff')\n\nconst TRACK_Z = 1.35\nconst TRACK_W = 0.74\nconst WHEEL_R = 0.36\nconst WHEEL_X = [-2.1, -1.05, 0, 1.05, 2.1]\nconst SPROCKET = [2.85, 0.62, 0.3]\nconst IDLER = [-2.85, 0.62, 0.3]\nconst TRACK_TILE = 0.9 // meters of track per texture repeat\n\nexport const TURRET_PIVOT = new Vector3(-0.35, 1.6, 0)\nexport const GUN_PIVOT = new Vector3(1.55, 0.42, 0)\nexport const MUZZLE = new Vector3(4.7, 0, 0)\n\n// hexagonal section (y,z): bottom edge, vertical sides, bevelled top\nconst sec = (yb, yt, z, b, zb = z) => [\n [yb, -zb],\n [yb, zb],\n [yt - b, z],\n [yt, z - b * 1.6],\n [yt, -(z - b * 1.6)],\n [yt - b, -z],\n]\n\n// Loft rings along x (each ring same point count) + end caps.\nfunction loft(b, rings, color, glow = 0, topColor = null) {\n let cx = 0, cy = 0, n = 0\n for (const r of rings) for (const p of r.pts) { cx += r.x; cy += p[0]; n++ }\n const ins = [cx / n, cy / n, 0]\n for (let i = 0; i < rings.length - 1; i++) {\n const A = rings[i], B = rings[i + 1]\n const m = A.pts.length\n for (let k = 0; k < m; k++) {\n const k1 = (k + 1) % m\n const a0 = [A.x, A.pts[k][0], A.pts[k][1]]\n const a1 = [A.x, A.pts[k1][0], A.pts[k1][1]]\n const b0 = [B.x, B.pts[k][0], B.pts[k][1]]\n const b1 = [B.x, B.pts[k1][0], B.pts[k1][1]]\n const isTop = topColor && A.pts[k][0] === A.pts[k1][0] && A.pts[k][0] > ins[1]\n b.quad(a0, a1, b1, b0, ins, isTop ? topColor : color, glow)\n }\n }\n for (const r of [rings[0], rings[rings.length - 1]]) {\n const c = [r.x, 0, 0]\n for (const p of r.pts) c[1] += p[0] / r.pts.length\n for (let k = 0; k < r.pts.length; k++) {\n const p0 = r.pts[k], p1 = r.pts[(k + 1) % r.pts.length]\n b.tri(c, [r.x, p0[0], p0[1]], [r.x, p1[0], p1[1]], ins, color, glow)\n }\n }\n}\n\n// Cylinder along x.\nfunction cylX(b, x0, x1, r0, r1, seg, color, y = 0, z = 0, glow = 0) {\n const ins = [(x0 + x1) / 2, y, z]\n for (let i = 0; i < seg; i++) {\n const a0 = (i / seg) * Math.PI * 2 + Math.PI / seg\n const a1 = ((i + 1) / seg) * Math.PI * 2 + Math.PI / seg\n const p = (x, r, a) => [x, y + Math.cos(a) * r, z + Math.sin(a) * r]\n b.quad(p(x0, r0, a0), p(x0, r0, a1), p(x1, r1, a1), p(x1, r1, a0), ins, color, glow)\n b.tri([x0, y, z], p(x0, r0, a0), p(x0, r0, a1), [x1, y, z], color, glow)\n b.tri([x1, y, z], p(x1, r1, a0), p(x1, r1, a1), [x0, y, z], color, glow)\n }\n}\n\n// Cylinder along z centred at origin (wheel).\nfunction cylZ(b, z0, z1, r, seg, color, cx = 0, cy = 0) {\n const ins = [cx, cy, (z0 + z1) / 2]\n for (let i = 0; i < seg; i++) {\n const a0 = (i / seg) * Math.PI * 2\n const a1 = ((i + 1) / seg) * Math.PI * 2\n const p = (z, a) => [cx + Math.cos(a) * r, cy + Math.sin(a) * r, z]\n b.quad(p(z0, a0), p(z0, a1), p(z1, a1), p(z1, a0), ins, color)\n b.tri([cx, cy, z0], p(z0, a0), p(z0, a1), [cx, cy, z1], color)\n b.tri([cx, cy, z1], p(z1, a0), p(z1, a1), [cx, cy, z0], color)\n }\n}\n\nfunction buildHull() {\n const b = new GeoBuilder()\n // lower hull between the tracks\n loft(b, [\n { x: -3.0, pts: [[0.5, -0.9], [0.5, 0.9], [1.0, 0.98], [1.0, -0.98]] },\n { x: 2.55, pts: [[0.45, -0.9], [0.45, 0.9], [1.0, 0.98], [1.0, -0.98]] },\n { x: 3.15, pts: [[0.85, -0.85], [0.85, 0.85], [1.0, 0.95], [1.0, -0.95]] },\n ], G)\n // upper hull / deck over the tracks with sloped glacis\n loft(b, [\n { x: -3.15, pts: sec(0.98, 1.48, 1.68, 0.1) },\n { x: -2.95, pts: sec(0.95, 1.6, 1.8, 0.14) },\n { x: 1.85, pts: sec(0.95, 1.6, 1.8, 0.14) },\n { x: 3.3, pts: sec(0.95, 1.1, 1.72, 0.06) },\n ], W, 0, W2)\n // engine deck grilles + exhausts\n b.box(-2.2, 1.62, 0, 1.1, 0.05, 1.7, D)\n for (let i = 0; i < 4; i++) b.box(-2.65 + i * 0.3, 1.66, 0, 0.12, 0.03, 1.6, G)\n b.box(-3.18, 1.22, 0.9, 0.12, 0.28, 0.5, D)\n b.box(-3.18, 1.22, -0.9, 0.12, 0.28, 0.5, D)\n // side skirts (cover the upper run of the tracks)\n for (const s of [-1, 1]) {\n loft(b, [\n { x: -2.95, pts: [[0.62, s * 1.82], [0.98, s * 1.8], [0.98, s * 1.88], [0.62, s * 1.9]] },\n { x: 2.35, pts: [[0.62, s * 1.82], [0.98, s * 1.8], [0.98, s * 1.88], [0.62, s * 1.9]] },\n ], W2)\n // fender storage boxes\n b.box(-1.3, 1.72, s * 1.4, 1.3, 0.24, 0.5, G)\n b.box(0.7, 1.7, s * 1.45, 0.7, 0.2, 0.4, W2)\n // tow hooks / front bumpers\n b.box(3.2, 0.92, s * 1.25, 0.25, 0.14, 0.22, D)\n }\n // driver hatch\n b.prism(1.35, 1.6, 0.55, 0.32, 0.32, 0.08, 6, G)\n b.box(1.8, 1.61, -0.45, 0.35, 0.06, 0.5, D)\n return b.build()\n}\n\nfunction buildHullAccent() {\n const b = new GeoBuilder()\n for (const s of [-1, 1]) {\n b.box(-0.3, 1.0, s * 1.905, 5.0, 0.07, 0.03, K, 1) // skirt top stripe\n b.box(-0.3, 0.66, s * 1.915, 5.0, 0.04, 0.03, K, 1)\n b.box(-3.19, 1.4, s * 1.3, 0.04, 0.12, 0.4, K, 1) // rear lights\n }\n b.box(2.9, 1.24, 0, 0.6, 0.02, 0.22, K, 1) // glacis chevron\n return b.build()\n}\n\nfunction buildLamps() {\n const b = new GeoBuilder()\n for (const s of [-1, 1]) {\n b.box(3.24, 1.16, s * 1.3, 0.12, 0.16, 0.26, K, 1)\n }\n return b.build()\n}\n\nfunction buildTurret() {\n const b = new GeoBuilder()\n loft(b, [\n { x: -2.25, pts: sec(0.05, 0.62, 1.0, 0.1, 0.95) },\n { x: -1.55, pts: sec(0.0, 0.8, 1.32, 0.14, 1.25) },\n { x: 0.85, pts: sec(0.0, 0.8, 1.35, 0.14, 1.28) },\n { x: 1.75, pts: sec(0.05, 0.55, 0.85, 0.08, 0.95) },\n ], W, 0, W2)\n // ring base\n b.prism(-0.2, -0.08, 0, 1.15, 1.15, 0.1, 10, D)\n // commander cupola + hatch\n b.prism(-0.75, 0.8, 0.55, 0.4, 0.4, 0.22, 8, G, 0, 0.9)\n b.prism(-0.75, 1.02, 0.55, 0.3, 0.3, 0.07, 8, W2)\n b.box(-0.2, 0.86, -0.6, 0.6, 0.1, 0.45, G)\n // gunner sight\n b.box(0.55, 0.92, -0.75, 0.42, 0.26, 0.3, G)\n b.box(0.77, 0.94, -0.75, 0.02, 0.16, 0.22, D)\n // bustle rack\n b.box(-2.3, 0.45, 0, 0.32, 0.4, 1.7, D)\n // smoke dischargers\n for (const s of [-1, 1]) {\n for (let i = 0; i < 3; i++) b.box(1.0 - i * 0.12, 0.58, s * (1.18 + i * 0.03), 0.22, 0.12, 0.12, G)\n }\n // antenna\n b.box(-1.85, 1.5, -0.85, 0.04, 1.5, 0.04, D)\n return b.build()\n}\n\nfunction buildTurretAccent() {\n const b = new GeoBuilder()\n for (const s of [-1, 1]) {\n b.box(-0.35, 0.42, s * 1.34, 2.3, 0.1, 0.03, K, 1)\n }\n b.box(-2.42, 0.45, 0, 0.03, 0.08, 1.6, K, 1)\n b.box(-1.85, 2.27, -0.85, 0.08, 0.08, 0.08, K, 1) // antenna tip\n return b.build()\n}\n\nfunction buildGun() {\n const b = new GeoBuilder()\n // mantlet\n loft(b, [\n { x: -0.05, pts: [[-0.32, -0.5], [-0.32, 0.5], [0.3, 0.5], [0.3, -0.5]] },\n { x: 0.55, pts: [[-0.22, -0.36], [-0.22, 0.36], [0.22, 0.36], [0.22, -0.36]] },\n ], G)\n cylX(b, 0.5, 4.3, 0.15, 0.12, 10, W2)\n cylX(b, 1.9, 2.55, 0.21, 0.21, 10, G)\n cylX(b, 4.25, 4.7, 0.19, 0.19, 8, M)\n b.box(4.47, 0, 0, 0.18, 0.3, 0.46, D)\n return b.build()\n}\n\nfunction buildWheel() {\n const b = new GeoBuilder()\n const h = TRACK_W * 0.42\n cylZ(b, -h, h, WHEEL_R, 10, D)\n cylZ(b, h - 0.01, h + 0.04, WHEEL_R * 0.66, 10, W2)\n cylZ(b, -h - 0.04, -h + 0.01, WHEEL_R * 0.66, 10, W2)\n // spokes (make rotation readable)\n for (let i = 0; i < 3; i++) {\n const a = (i / 3) * Math.PI\n const c = Math.cos(a), s = Math.sin(a)\n const L = WHEEL_R * 0.55\n const w = 0.07\n const zf = h + 0.06\n for (const zz of [zf, -zf]) {\n const ins = [0, 0, zz > 0 ? zz - 0.1 : zz + 0.1]\n b.quad(\n [-c * L - s * w, -s * L + c * w, zz],\n [c * L - s * w, s * L + c * w, zz],\n [c * L + s * w, s * L - c * w, zz],\n [-c * L + s * w, -s * L - c * w, zz],\n ins,\n G,\n )\n }\n }\n cylZ(b, -h - 0.08, h + 0.08, 0.08, 6, M)\n return b.build()\n}\n\n// Track loop (outer surface only) with uv along the loop.\nfunction buildTrack() {\n const pts = []\n const arc = (cx, cy, r, a0, a1, n) => {\n for (let i = 0; i <= n; i++) {\n const a = a0 + ((a1 - a0) * i) / n\n pts.push([cx + Math.cos(a) * r, cy + Math.sin(a) * r])\n }\n }\n const rr = 0.38\n pts.push([-2.1, -0.0])\n pts.push([2.1, -0.0])\n arc(SPROCKET[0], SPROCKET[1], rr, -2.0, Math.PI / 2, 6)\n arc(IDLER[0], IDLER[1], rr, Math.PI / 2, Math.PI + 2.0, 6)\n pts.push(pts[0])\n const lens = [0]\n for (let i = 1; i < pts.length; i++) {\n lens.push(lens[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]))\n }\n const P = [], Nn = [], UV = []\n for (const side of [-1, 1]) {\n const z0 = side * TRACK_Z - TRACK_W / 2\n const z1 = side * TRACK_Z + TRACK_W / 2\n for (let i = 0; i < pts.length - 1; i++) {\n const [x0, y0] = pts[i]\n const [x1, y1] = pts[i + 1]\n // loop is CCW in (x,y) seen from +z → outward normal = (dy, -dx)\n let nx = y1 - y0, ny = -(x1 - x0)\n const l = Math.hypot(nx, ny) || 1\n nx /= l\n ny /= l\n const u0 = lens[i] / TRACK_TILE\n const u1 = lens[i + 1] / TRACK_TILE\n const quad = [\n [x0, y0, z0, u0, 0], [x1, y1, z0, u1, 0], [x1, y1, z1, u1, 1],\n [x0, y0, z0, u0, 0], [x1, y1, z1, u1, 1], [x0, y0, z1, u0, 1],\n ]\n for (const q of quad) {\n P.push(q[0], q[1] + 0.02, q[2])\n Nn.push(nx, ny, 0)\n UV.push(q[3], q[4])\n }\n }\n }\n const g = new BufferGeometry()\n g.setAttribute('position', new BufferAttribute(new Float32Array(P), 3))\n g.setAttribute('normal', new BufferAttribute(new Float32Array(Nn), 3))\n g.setAttribute('uv', new BufferAttribute(new Float32Array(UV), 2))\n g.computeBoundingSphere()\n return g\n}\n\n// Local wheel transforms (position + radius scale) for one tank: 14 wheels.\nexport const WHEELS = (() => {\n const list = []\n for (const s of [-1, 1]) {\n for (const x of WHEEL_X) list.push({ x, y: WHEEL_R, z: s * TRACK_Z, r: WHEEL_R })\n list.push({ x: SPROCKET[0], y: SPROCKET[1], z: s * TRACK_Z, r: SPROCKET[2] })\n list.push({ x: IDLER[0], y: IDLER[1], z: s * TRACK_Z, r: IDLER[2] })\n }\n return list\n})()\n\nexport function createTankGeometries() {\n const g = {\n hull: buildHull(),\n hullAccent: buildHullAccent(),\n lamps: buildLamps(),\n turret: buildTurret(),\n turretAccent: buildTurretAccent(),\n gun: buildGun(),\n wheel: buildWheel(),\n track: buildTrack(),\n }\n g.dispose = () => {\n for (const k of Object.keys(g)) if (g[k] && g[k].isBufferGeometry) g[k].dispose()\n }\n return g\n}\n\nconst CHAR = lin('#3a3431')\nconst CHAR_ACCENT = lin('#2a201b')\n\n/** One tank instance (scene graph + own materials). */\nexport function createTank(geos, shared, { id, color = 'green', quality = 'high' }) {\n const team = TEAM[color] || TEAM.green\n const root = new Group()\n root.scale.setScalar(TANK_SCALE)\n const body = new Group() // tilt + hull yaw\n root.add(body)\n const turret = new Group()\n turret.position.copy(TURRET_PIVOT)\n const gun = new Group()\n gun.position.copy(GUN_PIVOT)\n turret.add(gun)\n body.add(turret)\n\n const accentMat = new MeshBasicMaterial({ color: team.accent.clone() })\n accentMat.color.multiplyScalar(1.35)\n const trackTex = shared.trackTex.clone()\n trackTex.needsUpdate = true\n let bodyMat = null\n let trackMat = null\n\n const hull = new Mesh(geos.hull, null)\n const hullAccent = new Mesh(geos.hullAccent, accentMat)\n const lamps = new Mesh(geos.lamps, shared.lampMat)\n const track = new Mesh(geos.track, null)\n const turretMesh = new Mesh(geos.turret, null)\n const turretAccent = new Mesh(geos.turretAccent, accentMat)\n const gunMesh = new Mesh(geos.gun, null)\n body.add(hull, hullAccent, lamps, track)\n turret.add(turretMesh, turretAccent)\n gun.add(gunMesh)\n const casters = [hull, track, turretMesh, gunMesh]\n\n function makeMaterials(q) {\n if (bodyMat) bodyMat.dispose()\n if (trackMat) trackMat.dispose()\n if (q === 'low') {\n bodyMat = new MeshLambertMaterial({ vertexColors: true, color: team.tint.clone() })\n trackMat = new MeshLambertMaterial({ map: trackTex, color: lin('#9a9a9a'), side: DoubleSide })\n } else {\n bodyMat = new MeshStandardMaterial({\n vertexColors: true,\n color: team.tint.clone(),\n roughness: 0.62,\n metalness: 0.18,\n flatShading: true,\n })\n trackMat = new MeshStandardMaterial({\n map: trackTex,\n color: lin('#a0a0a0'),\n roughness: 0.85,\n metalness: 0.2,\n side: DoubleSide,\n })\n }\n hull.material = bodyMat\n turretMesh.material = bodyMat\n gunMesh.material = bodyMat\n track.material = trackMat\n if (state.charred) applyCharred()\n }\n\n const state = {\n id,\n color,\n alive: true,\n charred: false,\n hp: 100,\n maxHp: 100,\n x: 0,\n y: 0,\n z: 0,\n hullYaw: 0, // deg\n yaw: 0, // turret absolute deg\n pitch: 10, // deg\n targetYaw: null,\n targetPitch: null,\n recoil: 0,\n hitFlash: 0,\n trackDist: 0,\n wheelSpin: 0,\n smokeAcc: 0,\n blown: null, // { pos:Vector3, vel:Vector3, rot:Vector3, spin:Vector3, landed }\n }\n\n function applyCharred() {\n bodyMat.color.copy(CHAR)\n accentMat.color.copy(CHAR_ACCENT)\n lamps.visible = false\n if (trackMat) trackMat.color.copy(lin('#3a3836'))\n }\n\n makeMaterials(quality)\n\n const up = new Vector3(0, 1, 0)\n const nrm = new Vector3()\n const qTilt = new Quaternion()\n const qYaw = new Quaternion()\n const tiltTarget = new Quaternion()\n let tiltInit = false\n\n /** Place hull on terrain: position + tilt from terrain normal. */\n function place(terrain, dt = 0) {\n root.position.set(state.x, state.y, state.z)\n terrain.normalAt(state.x, state.z, nrm, 2.2)\n tiltTarget.setFromUnitVectors(up, nrm)\n if (!tiltInit || dt <= 0) {\n qTilt.copy(tiltTarget)\n tiltInit = true\n } else {\n qTilt.slerp(tiltTarget, 1 - Math.exp(-10 * dt))\n }\n qYaw.setFromAxisAngle(up, (-state.hullYaw * Math.PI) / 180)\n body.quaternion.copy(qTilt).multiply(qYaw)\n turret.rotation.y = (-(state.yaw - state.hullYaw) * Math.PI) / 180\n gun.rotation.z = (state.pitch * Math.PI) / 180\n gun.position.x = GUN_PIVOT.x - state.recoil * 0.55\n }\n\n function setCharred(on) {\n if (state.charred === on) return\n state.charred = on\n if (on) applyCharred()\n else {\n bodyMat.color.copy(team.tint)\n accentMat.color.copy(team.accent).multiplyScalar(1.35)\n lamps.visible = true\n trackMat.color.copy(lin(quality === 'low' ? '#9a9a9a' : '#a0a0a0'))\n }\n }\n\n function setFlash(k) {\n bodyMat.emissive.setRGB(1.0 * k, 0.45 * k, 0.2 * k)\n }\n\n function setShadows(on) {\n for (const m of casters) {\n m.castShadow = on\n m.receiveShadow = on\n }\n }\n\n const tmpM = new Matrix4()\n const tmpQ = new Quaternion()\n const tmpP = new Vector3()\n const tmpS = new Vector3()\n const zAxis = new Vector3(0, 0, 1)\n /** Write 14 wheel matrices into an InstancedMesh starting at offset. */\n function writeWheels(mesh, offset) {\n body.updateWorldMatrix(true, false)\n for (let i = 0; i < WHEELS.length; i++) {\n const w = WHEELS[i]\n tmpQ.setFromAxisAngle(zAxis, -state.trackDist / w.r)\n tmpS.setScalar(w.r / WHEEL_R)\n tmpP.set(w.x, w.y, w.z)\n tmpM.compose(tmpP, tmpQ, tmpS)\n tmpM.premultiply(body.matrixWorld)\n mesh.setMatrixAt(offset + i, tmpM)\n }\n }\n\n return {\n id,\n team,\n root,\n body,\n turret,\n gun,\n turretMesh,\n turretAccent,\n state,\n trackTex,\n place,\n setCharred,\n setFlash,\n setShadows,\n writeWheels,\n setQuality(q) {\n quality = q\n makeMaterials(q)\n },\n scrollTracks(d) {\n state.trackDist += d\n trackTex.offset.x = -state.trackDist / TRACK_TILE\n },\n muzzleWorld(out) {\n gun.updateWorldMatrix(true, false)\n return out.copy(MUZZLE).applyMatrix4(gun.matrixWorld)\n },\n gunDirWorld(out) {\n gun.updateWorldMatrix(true, false)\n return out.set(1, 0, 0).transformDirection(gun.matrixWorld)\n },\n localToWorld(out, x, y, z) {\n body.updateWorldMatrix(true, false)\n return out.set(x, y, z).applyMatrix4(body.matrixWorld)\n },\n dispose() {\n bodyMat.dispose()\n trackMat.dispose()\n accentMat.dispose()\n trackTex.dispose()\n root.removeFromParent()\n },\n }\n}\n\nexport function createTankShared() {\n return {\n trackTex: trackTexture(),\n lampMat: new MeshBasicMaterial({ color: lin('#fff1c2').multiplyScalar(1.6) }),\n dispose() {\n this.trackTex.dispose()\n this.lampMat.dispose()\n },\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { r as e } from "./createPeerLink-DBVs_yvQ.js";
2
2
  import { useEffect as t, useId as n, useRef as r, useState as i } from "react";
3
- import { CheckCircle2 as a, Link2 as o, Loader2 as s, ShieldCheck as c, X as l } from "lucide-react";
4
- import { jsx as u, jsxs as d } from "react/jsx-runtime";
3
+ import { jsx as a, jsxs as o } from "react/jsx-runtime";
4
+ import { CheckCircle2 as s, Link2 as c, Loader2 as l, ShieldCheck as u, X as d } from "lucide-react";
5
5
  // src/games/tanks-live/ManualTurnDialog.jsx
6
6
  var f = "{\n \"iceServers\": [{\n \"urls\": [\"turn:turn.cloudflare.com:3478?transport=udp\"],\n \"username\": \"usuario-temporal\",\n \"credential\": \"credencial-temporal\"\n }]\n}";
7
7
  function p({ open: p, onClose: m, onApply: h, manualTurn: g }) {
@@ -41,7 +41,7 @@ function p({ open: p, onClose: m, onApply: h, manualTurn: g }) {
41
41
  r === v.current && j(!1);
42
42
  }
43
43
  }, F = g?.status === "applying" ? "Aplicando configuración en ambos jugadores…" : g?.status === "waiting_peer" ? "Esperando al otro jugador para compartir la configuración…" : "Sincronizando con el otro jugador…";
44
- return /* @__PURE__ */ d("dialog", {
44
+ return /* @__PURE__ */ o("dialog", {
45
45
  ref: _,
46
46
  className: "tl-turn-dialog",
47
47
  "aria-labelledby": y,
@@ -55,38 +55,38 @@ function p({ open: p, onClose: m, onApply: h, manualTurn: g }) {
55
55
  (e.clientX < t.left || e.clientX > t.right || e.clientY < t.top || e.clientY > t.bottom) && m();
56
56
  }
57
57
  },
58
- children: [/* @__PURE__ */ d("header", {
58
+ children: [/* @__PURE__ */ o("header", {
59
59
  className: "tl-turn-dialog__header",
60
60
  children: [
61
- /* @__PURE__ */ u("span", {
61
+ /* @__PURE__ */ a("span", {
62
62
  className: "tl-turn-dialog__icon",
63
63
  "aria-hidden": "true",
64
- children: /* @__PURE__ */ u(o, { size: 22 })
64
+ children: /* @__PURE__ */ a(c, { size: 22 })
65
65
  }),
66
- /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("h2", {
66
+ /* @__PURE__ */ o("div", { children: [/* @__PURE__ */ a("h2", {
67
67
  id: y,
68
68
  children: "Configurar conexión alternativa"
69
- }), /* @__PURE__ */ u("p", { children: "Se comparte con el otro jugador." })] }),
70
- /* @__PURE__ */ u("button", {
69
+ }), /* @__PURE__ */ a("p", { children: "Se comparte con el otro jugador." })] }),
70
+ /* @__PURE__ */ a("button", {
71
71
  type: "button",
72
72
  className: "tl-turn-dialog__close",
73
73
  "aria-label": "Cerrar configuración de conexión",
74
74
  onClick: m,
75
- children: /* @__PURE__ */ u(l, { size: 18 })
75
+ children: /* @__PURE__ */ a(d, { size: 18 })
76
76
  })
77
77
  ]
78
- }), /* @__PURE__ */ d("form", {
78
+ }), /* @__PURE__ */ o("form", {
79
79
  onSubmit: P,
80
- children: [/* @__PURE__ */ d("div", {
80
+ children: [/* @__PURE__ */ o("div", {
81
81
  className: "tl-turn-dialog__body",
82
82
  children: [
83
- /* @__PURE__ */ u("p", {
83
+ /* @__PURE__ */ a("p", {
84
84
  id: b,
85
85
  children: "Pega el JSON de credenciales temporales de Cloudflare TURN. Se compartirá con el otro jugador para buscar una conexión alternativa."
86
86
  }),
87
- /* @__PURE__ */ d("label", {
87
+ /* @__PURE__ */ o("label", {
88
88
  className: "tl-turn-dialog__field",
89
- children: ["Configuración JSON", /* @__PURE__ */ u("textarea", {
89
+ children: ["Configuración JSON", /* @__PURE__ */ a("textarea", {
90
90
  autoFocus: !0,
91
91
  value: S,
92
92
  onChange: (e) => {
@@ -105,22 +105,22 @@ function p({ open: p, onClose: m, onApply: h, manualTurn: g }) {
105
105
  "aria-describedby": O ? x : void 0
106
106
  })]
107
107
  }),
108
- /* @__PURE__ */ d("div", {
108
+ /* @__PURE__ */ o("div", {
109
109
  className: "tl-turn-dialog__privacy",
110
- children: [/* @__PURE__ */ u(c, {
110
+ children: [/* @__PURE__ */ a(u, {
111
111
  size: 16,
112
112
  "aria-hidden": "true"
113
- }), /* @__PURE__ */ u("span", { children: "Solo credenciales temporales. No pegues tu API Token ni secretos permanentes." })]
113
+ }), /* @__PURE__ */ a("span", { children: "Solo credenciales temporales. No pegues tu API Token ni secretos permanentes." })]
114
114
  }),
115
- /* @__PURE__ */ d("label", {
115
+ /* @__PURE__ */ o("label", {
116
116
  className: "tl-turn-dialog__field",
117
117
  children: [
118
118
  "Validez restante en segundos ",
119
- /* @__PURE__ */ u("span", {
119
+ /* @__PURE__ */ a("span", {
120
120
  className: "tl-turn-dialog__optional",
121
121
  children: "Opcional"
122
122
  }),
123
- /* @__PURE__ */ u("input", {
123
+ /* @__PURE__ */ a("input", {
124
124
  type: "number",
125
125
  min: "1",
126
126
  max: "172800",
@@ -133,57 +133,57 @@ function p({ open: p, onClose: m, onApply: h, manualTurn: g }) {
133
133
  disabled: A,
134
134
  placeholder: "Ej. 3600"
135
135
  }),
136
- /* @__PURE__ */ u("small", { children: "Déjalo vacío si no conoces la caducidad. Si dejan de funcionar, pega credenciales nuevas." })
136
+ /* @__PURE__ */ a("small", { children: "Déjalo vacío si no conoces la caducidad. Si dejan de funcionar, pega credenciales nuevas." })
137
137
  ]
138
138
  }),
139
- /* @__PURE__ */ d("label", {
139
+ /* @__PURE__ */ o("label", {
140
140
  className: "tl-turn-dialog__debug",
141
- children: [/* @__PURE__ */ u("input", {
141
+ children: [/* @__PURE__ */ a("input", {
142
142
  type: "checkbox",
143
143
  checked: E,
144
144
  onChange: (e) => D(e.target.checked),
145
145
  disabled: A
146
- }), /* @__PURE__ */ d("span", { children: [/* @__PURE__ */ u("strong", { children: "Forzar relay para diagnóstico" }), /* @__PURE__ */ u("small", { children: "Normalmente se permite P2P. Actívalo solo para comprobar TURN." })] })]
146
+ }), /* @__PURE__ */ o("span", { children: [/* @__PURE__ */ a("strong", { children: "Forzar relay para diagnóstico" }), /* @__PURE__ */ a("small", { children: "Normalmente se permite P2P. Actívalo solo para comprobar TURN." })] })]
147
147
  }),
148
- A && /* @__PURE__ */ d("p", {
148
+ A && /* @__PURE__ */ o("p", {
149
149
  className: "tl-turn-dialog__feedback",
150
150
  role: "status",
151
- children: [/* @__PURE__ */ u(s, {
151
+ children: [/* @__PURE__ */ a(l, {
152
152
  size: 16,
153
153
  className: "tl-connection-spinner",
154
154
  "aria-hidden": "true"
155
155
  }), F]
156
156
  }),
157
- M && /* @__PURE__ */ d("p", {
157
+ M && /* @__PURE__ */ o("p", {
158
158
  className: "tl-turn-dialog__feedback tl-turn-dialog__feedback--success",
159
159
  role: "status",
160
- children: [/* @__PURE__ */ u(a, {
160
+ children: [/* @__PURE__ */ a(s, {
161
161
  size: 17,
162
162
  "aria-hidden": "true"
163
163
  }), "Configuración compartida y aplicada en ambos jugadores. La ruta utilizada se mostrará al conectar."]
164
164
  }),
165
- O && /* @__PURE__ */ u("p", {
165
+ O && /* @__PURE__ */ a("p", {
166
166
  id: x,
167
167
  className: "tl-turn-dialog__error",
168
168
  role: "alert",
169
169
  children: O
170
170
  })
171
171
  ]
172
- }), /* @__PURE__ */ d("footer", {
172
+ }), /* @__PURE__ */ o("footer", {
173
173
  className: "tl-turn-dialog__footer",
174
- children: [/* @__PURE__ */ u("button", {
174
+ children: [/* @__PURE__ */ a("button", {
175
175
  type: "button",
176
176
  onClick: m,
177
177
  children: M ? "Listo" : "Cancelar"
178
- }), /* @__PURE__ */ d("button", {
178
+ }), /* @__PURE__ */ o("button", {
179
179
  type: "submit",
180
180
  className: "tl-turn-dialog__apply",
181
181
  disabled: A || !S.trim(),
182
- children: [A ? /* @__PURE__ */ u(s, {
182
+ children: [A ? /* @__PURE__ */ a(l, {
183
183
  size: 16,
184
184
  className: "tl-connection-spinner",
185
185
  "aria-hidden": "true"
186
- }) : /* @__PURE__ */ u(o, {
186
+ }) : /* @__PURE__ */ a(c, {
187
187
  size: 16,
188
188
  "aria-hidden": "true"
189
189
  }), "Aplicar y reconectar"]
@@ -258,4 +258,4 @@ function g(e = {}) {
258
258
  }
259
259
  export { g as n, p as r, h as t };
260
260
 
261
- //# sourceMappingURL=tanks-live-BcKmrhnV.js.map
261
+ //# sourceMappingURL=tanks-live-BRhYW4MG.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tanks-live-BcKmrhnV.js","names":["parseManualTurnConfig","PLACEHOLDER","ManualTurnDialog","dialogRef","useRef","requestRef","titleId","useId","descriptionId","errorId","json","setJson","useState","ttl","setTtl","relayOnly","setRelayOnly","error","setError","busy","setBusy","applied","setApplied","dialog","open","opener","submit","event","options","validationError","request","onApply","applyError","progress","manualTurn","onClose","bounds","Link2","X","ShieldCheck","Loader2","CheckCircle2","code","value","connectionDiagnostics","route","net","candidates","manual","connectionHint"],"sources":["../../src/games/tanks-live/ManualTurnDialog.jsx","../../src/games/tanks-live/net/connectionDiagnostics.js"],"sourcesContent":["/* eslint-disable react/prop-types */\nimport { useEffect, useId, useRef, useState } from 'react';\nimport { CheckCircle2, Link2, Loader2, ShieldCheck, X } from 'lucide-react';\nimport { parseManualTurnConfig } from '../../net/webrtc/manualTurnConfig.js';\n\nconst PLACEHOLDER = '{\\n \"iceServers\": [{\\n \"urls\": [\"turn:turn.cloudflare.com:3478?transport=udp\"],\\n \"username\": \"usuario-temporal\",\\n \"credential\": \"credencial-temporal\"\\n }]\\n}';\n\nexport default function ManualTurnDialog({ open, onClose, onApply, manualTurn }) {\n const dialogRef = useRef(null);\n const requestRef = useRef(0);\n const titleId = useId();\n const descriptionId = useId();\n const errorId = useId();\n const [json, setJson] = useState('');\n const [ttl, setTtl] = useState('');\n const [relayOnly, setRelayOnly] = useState(false);\n const [error, setError] = useState('');\n const [busy, setBusy] = useState(false);\n const [applied, setApplied] = useState(false);\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!open) {\n requestRef.current += 1;\n setJson(''); setTtl(''); setRelayOnly(false); setError(''); setBusy(false); setApplied(false);\n if (dialog?.open) dialog.close();\n return undefined;\n }\n const opener = document.activeElement;\n dialog.showModal();\n return () => {\n requestRef.current += 1;\n if (dialog.open) dialog.close();\n if (opener instanceof HTMLElement && opener.isConnected) opener.focus({ preventScroll: true });\n };\n }, [open]);\n const submit = async (event) => {\n event.preventDefault();\n if (busy || !json.trim()) return;\n setError('');\n const options = { iceTransportPolicy: relayOnly ? 'relay' : 'all', ...(ttl.trim() ? { ttlSeconds: Number(ttl) } : {}) };\n try { parseManualTurnConfig(json, options); }\n catch (validationError) { setError(validationError.message); return; }\n const request = ++requestRef.current;\n setBusy(true); setApplied(false);\n try {\n await onApply(json, options);\n if (request !== requestRef.current) return;\n setJson(''); setTtl(''); setApplied(true);\n } catch (applyError) {\n if (request === requestRef.current) setError(applyError.message || 'No se pudo compartir la configuración. Inténtalo de nuevo.');\n } finally {\n if (request === requestRef.current) setBusy(false);\n }\n };\n const progress = manualTurn?.status === 'applying' ? 'Aplicando configuración en ambos jugadores…'\n : manualTurn?.status === 'waiting_peer' ? 'Esperando al otro jugador para compartir la configuración…'\n : 'Sincronizando con el otro jugador…';\n return <dialog ref={dialogRef} className=\"tl-turn-dialog\" aria-labelledby={titleId} aria-describedby={descriptionId}\n onCancel={(event) => { event.preventDefault(); onClose(); }}\n onClick={(event) => { if (event.target === event.currentTarget) { const bounds = event.currentTarget.getBoundingClientRect(); if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) onClose(); } }}>\n <header className=\"tl-turn-dialog__header\"><span className=\"tl-turn-dialog__icon\" aria-hidden=\"true\"><Link2 size={22} /></span><div><h2 id={titleId}>Configurar conexión alternativa</h2><p>Se comparte con el otro jugador.</p></div><button type=\"button\" className=\"tl-turn-dialog__close\" aria-label=\"Cerrar configuración de conexión\" onClick={onClose}><X size={18} /></button></header>\n <form onSubmit={submit}>\n <div className=\"tl-turn-dialog__body\">\n <p id={descriptionId}>Pega el JSON de credenciales temporales de Cloudflare TURN. Se compartirá con el otro jugador para buscar una conexión alternativa.</p>\n <label className=\"tl-turn-dialog__field\">Configuración JSON<textarea autoFocus value={json} onChange={(event) => { setJson(event.target.value); setError(''); setApplied(false); }} placeholder={PLACEHOLDER} maxLength={6000} rows={7} required={!applied} readOnly={busy} autoComplete=\"off\" autoCorrect=\"off\" autoCapitalize=\"off\" spellCheck={false} aria-invalid={Boolean(error)} aria-describedby={error ? errorId : undefined} /></label>\n <div className=\"tl-turn-dialog__privacy\"><ShieldCheck size={16} aria-hidden=\"true\" /><span>Solo credenciales temporales. No pegues tu API Token ni secretos permanentes.</span></div>\n <label className=\"tl-turn-dialog__field\">Validez restante en segundos <span className=\"tl-turn-dialog__optional\">Opcional</span><input type=\"number\" min=\"1\" max=\"172800\" step=\"1\" inputMode=\"numeric\" value={ttl} onChange={(event) => { setTtl(event.target.value); setError(''); }} disabled={busy} placeholder=\"Ej. 3600\" /><small>Déjalo vacío si no conoces la caducidad. Si dejan de funcionar, pega credenciales nuevas.</small></label>\n <label className=\"tl-turn-dialog__debug\"><input type=\"checkbox\" checked={relayOnly} onChange={(event) => setRelayOnly(event.target.checked)} disabled={busy} /><span><strong>Forzar relay para diagnóstico</strong><small>Normalmente se permite P2P. Actívalo solo para comprobar TURN.</small></span></label>\n {busy && <p className=\"tl-turn-dialog__feedback\" role=\"status\"><Loader2 size={16} className=\"tl-connection-spinner\" aria-hidden=\"true\" />{progress}</p>}\n {applied && <p className=\"tl-turn-dialog__feedback tl-turn-dialog__feedback--success\" role=\"status\"><CheckCircle2 size={17} aria-hidden=\"true\" />Configuración compartida y aplicada en ambos jugadores. La ruta utilizada se mostrará al conectar.</p>}\n {error && <p id={errorId} className=\"tl-turn-dialog__error\" role=\"alert\">{error}</p>}\n </div>\n <footer className=\"tl-turn-dialog__footer\"><button type=\"button\" onClick={onClose}>{applied ? 'Listo' : 'Cancelar'}</button><button type=\"submit\" className=\"tl-turn-dialog__apply\" disabled={busy || !json.trim()}>{busy ? <Loader2 size={16} className=\"tl-connection-spinner\" aria-hidden=\"true\" /> : <Link2 size={16} aria-hidden=\"true\" />}Aplicar y reconectar</button></footer>\n </form>\n </dialog>;\n}\n","// Never include SDP, candidate addresses, pasted JSON, passwords or TURN usernames.\nconst code = value => typeof value === 'string' && /^[a-z_]+$/.test(value) ? value : value ? 'connection_error' : null;\nexport function connectionDiagnostics(net = {}) {\n const route = net.route;\n const candidates = net.candidates ?? {};\n const manual = net.manualTurn ?? {};\n return {\n phase: net.phase ?? 'idle', ice: net.ice ?? 'new',\n connection: net.connection ?? 'new', signalingState: net.signalingState ?? 'stable', gathering: net.gathering ?? 'new',\n signaling: {\n sent: net.signals?.sent ?? 0, received: net.signals?.received ?? 0,\n sendFailures: net.signals?.sendFailures ?? 0, ignored: net.signals?.ignored ?? 0,\n invalid: net.signals?.invalid ?? 0, retransmits: net.signals?.retransmits ?? 0,\n duplicates: net.signals?.duplicates ?? 0, tooLarge: net.signals?.tooLarge ?? 0,\n },\n turnConfigured: Boolean(net.turnConfigured),\n configuration: { status: manual.status ?? 'idle', shared: Boolean(manual.shared), received: Boolean(manual.received), expiresAt: manual.expiresAt ?? null, expired: manual.expiresAt != null && manual.expiresAt <= Date.now() },\n route: route?.label ?? null,\n selectedPair: route ? { relay: Boolean(route.relay), localType: route.local ?? null, remoteType: route.remote ?? null, protocol: route.protocol ?? null, relayProtocol: route.relayProtocol ?? null } : null,\n candidates: { local: candidates.local ?? 0, remote: candidates.remote ?? 0, accepted: candidates.accepted ?? 0, rejected: candidates.rejected ?? 0, localRelay: candidates.localRelay ?? 0, remoteRelay: candidates.remoteRelay ?? 0 },\n iceErrors: (net.iceErrors ?? []).slice(-10).map(error => ({ code: Number(error.code) || 0, stage: error.stage === 'gathering' ? 'gathering' : 'remote_candidate' })),\n reconnects: net.reconnects ?? 0, restarts: net.restarts ?? 0,\n rttMs: net.rtt ?? net.statsRtt ?? null, connectionTimeMs: net.elapsedMs ?? 0,\n channels: { move: net.channels?.move ?? 'closed', events: net.channels?.events ?? 'closed' },\n movesSent: net.move?.sent ?? 0, movesReceived: net.move?.received ?? 0,\n error: code(net.error),\n };\n}\n\nexport function connectionHint(net = {}) {\n if (net.phase === 'connected') return '';\n if (net.manualTurn?.status === 'applying') return 'Aplicando la configuración de conexión alternativa.';\n if (net.manualTurn?.status === 'sharing') return 'Sincronizando la configuración con el otro jugador.';\n if (net.manualTurn?.status === 'waiting_peer') return 'Esperando al otro jugador para compartir la configuración.';\n if (net.error === 'turn_credentials_expired' || (net.manualTurn?.expiresAt != null && net.manualTurn.expiresAt <= Date.now())) return 'La validez indicada de TURN terminó. Pega credenciales temporales nuevas para reconectar.';\n if (net.signals?.sendFailures) return 'No se pudieron enviar señales por el chat. Revisa la conexión WebSocket y reintenta.';\n if (net.ice === 'checking' || net.ice === 'failed') return net.turnConfigured\n ? 'WebRTC aún no encuentra una ruta entre los jugadores. Puedes revisar o renovar la configuración TURN.'\n : 'WebRTC aún no encuentra una ruta entre los jugadores. Algunas redes necesitan un servidor TURN; puedes configurar una conexión alternativa.';\n if (net.phase === 'listening') return 'Esperando la conexión del rival. Ambos deben mantener abierta la partida y usar la misma versión del chat.';\n if (net.phase === 'failed') return 'No se pudo conectar. Revisa el diagnóstico de ambos jugadores y reintenta.';\n return 'Intercambiando señales de conexión por el chat. El combate empieza cuando conecta WebRTC.';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tanks-live-BRhYW4MG.js","names":["parseManualTurnConfig","PLACEHOLDER","ManualTurnDialog","dialogRef","useRef","requestRef","titleId","useId","descriptionId","errorId","json","setJson","useState","ttl","setTtl","relayOnly","setRelayOnly","error","setError","busy","setBusy","applied","setApplied","dialog","open","opener","submit","event","options","validationError","request","onApply","applyError","progress","manualTurn","onClose","bounds","Link2","X","ShieldCheck","Loader2","CheckCircle2","code","value","connectionDiagnostics","route","net","candidates","manual","connectionHint"],"sources":["../../src/games/tanks-live/ManualTurnDialog.jsx","../../src/games/tanks-live/net/connectionDiagnostics.js"],"sourcesContent":["/* eslint-disable react/prop-types */\nimport { useEffect, useId, useRef, useState } from 'react';\nimport { CheckCircle2, Link2, Loader2, ShieldCheck, X } from 'lucide-react';\nimport { parseManualTurnConfig } from '../../net/webrtc/manualTurnConfig.js';\n\nconst PLACEHOLDER = '{\\n \"iceServers\": [{\\n \"urls\": [\"turn:turn.cloudflare.com:3478?transport=udp\"],\\n \"username\": \"usuario-temporal\",\\n \"credential\": \"credencial-temporal\"\\n }]\\n}';\n\nexport default function ManualTurnDialog({ open, onClose, onApply, manualTurn }) {\n const dialogRef = useRef(null);\n const requestRef = useRef(0);\n const titleId = useId();\n const descriptionId = useId();\n const errorId = useId();\n const [json, setJson] = useState('');\n const [ttl, setTtl] = useState('');\n const [relayOnly, setRelayOnly] = useState(false);\n const [error, setError] = useState('');\n const [busy, setBusy] = useState(false);\n const [applied, setApplied] = useState(false);\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!open) {\n requestRef.current += 1;\n setJson(''); setTtl(''); setRelayOnly(false); setError(''); setBusy(false); setApplied(false);\n if (dialog?.open) dialog.close();\n return undefined;\n }\n const opener = document.activeElement;\n dialog.showModal();\n return () => {\n requestRef.current += 1;\n if (dialog.open) dialog.close();\n if (opener instanceof HTMLElement && opener.isConnected) opener.focus({ preventScroll: true });\n };\n }, [open]);\n const submit = async (event) => {\n event.preventDefault();\n if (busy || !json.trim()) return;\n setError('');\n const options = { iceTransportPolicy: relayOnly ? 'relay' : 'all', ...(ttl.trim() ? { ttlSeconds: Number(ttl) } : {}) };\n try { parseManualTurnConfig(json, options); }\n catch (validationError) { setError(validationError.message); return; }\n const request = ++requestRef.current;\n setBusy(true); setApplied(false);\n try {\n await onApply(json, options);\n if (request !== requestRef.current) return;\n setJson(''); setTtl(''); setApplied(true);\n } catch (applyError) {\n if (request === requestRef.current) setError(applyError.message || 'No se pudo compartir la configuración. Inténtalo de nuevo.');\n } finally {\n if (request === requestRef.current) setBusy(false);\n }\n };\n const progress = manualTurn?.status === 'applying' ? 'Aplicando configuración en ambos jugadores…'\n : manualTurn?.status === 'waiting_peer' ? 'Esperando al otro jugador para compartir la configuración…'\n : 'Sincronizando con el otro jugador…';\n return <dialog ref={dialogRef} className=\"tl-turn-dialog\" aria-labelledby={titleId} aria-describedby={descriptionId}\n onCancel={(event) => { event.preventDefault(); onClose(); }}\n onClick={(event) => { if (event.target === event.currentTarget) { const bounds = event.currentTarget.getBoundingClientRect(); if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) onClose(); } }}>\n <header className=\"tl-turn-dialog__header\"><span className=\"tl-turn-dialog__icon\" aria-hidden=\"true\"><Link2 size={22} /></span><div><h2 id={titleId}>Configurar conexión alternativa</h2><p>Se comparte con el otro jugador.</p></div><button type=\"button\" className=\"tl-turn-dialog__close\" aria-label=\"Cerrar configuración de conexión\" onClick={onClose}><X size={18} /></button></header>\n <form onSubmit={submit}>\n <div className=\"tl-turn-dialog__body\">\n <p id={descriptionId}>Pega el JSON de credenciales temporales de Cloudflare TURN. Se compartirá con el otro jugador para buscar una conexión alternativa.</p>\n <label className=\"tl-turn-dialog__field\">Configuración JSON<textarea autoFocus value={json} onChange={(event) => { setJson(event.target.value); setError(''); setApplied(false); }} placeholder={PLACEHOLDER} maxLength={6000} rows={7} required={!applied} readOnly={busy} autoComplete=\"off\" autoCorrect=\"off\" autoCapitalize=\"off\" spellCheck={false} aria-invalid={Boolean(error)} aria-describedby={error ? errorId : undefined} /></label>\n <div className=\"tl-turn-dialog__privacy\"><ShieldCheck size={16} aria-hidden=\"true\" /><span>Solo credenciales temporales. No pegues tu API Token ni secretos permanentes.</span></div>\n <label className=\"tl-turn-dialog__field\">Validez restante en segundos <span className=\"tl-turn-dialog__optional\">Opcional</span><input type=\"number\" min=\"1\" max=\"172800\" step=\"1\" inputMode=\"numeric\" value={ttl} onChange={(event) => { setTtl(event.target.value); setError(''); }} disabled={busy} placeholder=\"Ej. 3600\" /><small>Déjalo vacío si no conoces la caducidad. Si dejan de funcionar, pega credenciales nuevas.</small></label>\n <label className=\"tl-turn-dialog__debug\"><input type=\"checkbox\" checked={relayOnly} onChange={(event) => setRelayOnly(event.target.checked)} disabled={busy} /><span><strong>Forzar relay para diagnóstico</strong><small>Normalmente se permite P2P. Actívalo solo para comprobar TURN.</small></span></label>\n {busy && <p className=\"tl-turn-dialog__feedback\" role=\"status\"><Loader2 size={16} className=\"tl-connection-spinner\" aria-hidden=\"true\" />{progress}</p>}\n {applied && <p className=\"tl-turn-dialog__feedback tl-turn-dialog__feedback--success\" role=\"status\"><CheckCircle2 size={17} aria-hidden=\"true\" />Configuración compartida y aplicada en ambos jugadores. La ruta utilizada se mostrará al conectar.</p>}\n {error && <p id={errorId} className=\"tl-turn-dialog__error\" role=\"alert\">{error}</p>}\n </div>\n <footer className=\"tl-turn-dialog__footer\"><button type=\"button\" onClick={onClose}>{applied ? 'Listo' : 'Cancelar'}</button><button type=\"submit\" className=\"tl-turn-dialog__apply\" disabled={busy || !json.trim()}>{busy ? <Loader2 size={16} className=\"tl-connection-spinner\" aria-hidden=\"true\" /> : <Link2 size={16} aria-hidden=\"true\" />}Aplicar y reconectar</button></footer>\n </form>\n </dialog>;\n}\n","// Never include SDP, candidate addresses, pasted JSON, passwords or TURN usernames.\nconst code = value => typeof value === 'string' && /^[a-z_]+$/.test(value) ? value : value ? 'connection_error' : null;\nexport function connectionDiagnostics(net = {}) {\n const route = net.route;\n const candidates = net.candidates ?? {};\n const manual = net.manualTurn ?? {};\n return {\n phase: net.phase ?? 'idle', ice: net.ice ?? 'new',\n connection: net.connection ?? 'new', signalingState: net.signalingState ?? 'stable', gathering: net.gathering ?? 'new',\n signaling: {\n sent: net.signals?.sent ?? 0, received: net.signals?.received ?? 0,\n sendFailures: net.signals?.sendFailures ?? 0, ignored: net.signals?.ignored ?? 0,\n invalid: net.signals?.invalid ?? 0, retransmits: net.signals?.retransmits ?? 0,\n duplicates: net.signals?.duplicates ?? 0, tooLarge: net.signals?.tooLarge ?? 0,\n },\n turnConfigured: Boolean(net.turnConfigured),\n configuration: { status: manual.status ?? 'idle', shared: Boolean(manual.shared), received: Boolean(manual.received), expiresAt: manual.expiresAt ?? null, expired: manual.expiresAt != null && manual.expiresAt <= Date.now() },\n route: route?.label ?? null,\n selectedPair: route ? { relay: Boolean(route.relay), localType: route.local ?? null, remoteType: route.remote ?? null, protocol: route.protocol ?? null, relayProtocol: route.relayProtocol ?? null } : null,\n candidates: { local: candidates.local ?? 0, remote: candidates.remote ?? 0, accepted: candidates.accepted ?? 0, rejected: candidates.rejected ?? 0, localRelay: candidates.localRelay ?? 0, remoteRelay: candidates.remoteRelay ?? 0 },\n iceErrors: (net.iceErrors ?? []).slice(-10).map(error => ({ code: Number(error.code) || 0, stage: error.stage === 'gathering' ? 'gathering' : 'remote_candidate' })),\n reconnects: net.reconnects ?? 0, restarts: net.restarts ?? 0,\n rttMs: net.rtt ?? net.statsRtt ?? null, connectionTimeMs: net.elapsedMs ?? 0,\n channels: { move: net.channels?.move ?? 'closed', events: net.channels?.events ?? 'closed' },\n movesSent: net.move?.sent ?? 0, movesReceived: net.move?.received ?? 0,\n error: code(net.error),\n };\n}\n\nexport function connectionHint(net = {}) {\n if (net.phase === 'connected') return '';\n if (net.manualTurn?.status === 'applying') return 'Aplicando la configuración de conexión alternativa.';\n if (net.manualTurn?.status === 'sharing') return 'Sincronizando la configuración con el otro jugador.';\n if (net.manualTurn?.status === 'waiting_peer') return 'Esperando al otro jugador para compartir la configuración.';\n if (net.error === 'turn_credentials_expired' || (net.manualTurn?.expiresAt != null && net.manualTurn.expiresAt <= Date.now())) return 'La validez indicada de TURN terminó. Pega credenciales temporales nuevas para reconectar.';\n if (net.signals?.sendFailures) return 'No se pudieron enviar señales por el chat. Revisa la conexión WebSocket y reintenta.';\n if (net.ice === 'checking' || net.ice === 'failed') return net.turnConfigured\n ? 'WebRTC aún no encuentra una ruta entre los jugadores. Puedes revisar o renovar la configuración TURN.'\n : 'WebRTC aún no encuentra una ruta entre los jugadores. Algunas redes necesitan un servidor TURN; puedes configurar una conexión alternativa.';\n if (net.phase === 'listening') return 'Esperando la conexión del rival. Ambos deben mantener abierta la partida y usar la misma versión del chat.';\n if (net.phase === 'failed') return 'No se pudo conectar. Revisa el diagnóstico de ambos jugadores y reintenta.';\n return 'Intercambiando señales de conexión por el chat. El combate empieza cuando conecta WebRTC.';\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { t as e } from "./games-Bl_8raG6.js";
2
- import { i as t, o as n } from "./ChatContext-CVW58bzC.js";
3
- import { a as r, t as i } from "./connect4Sounds-BqEHULjv.js";
1
+ import { t as e } from "./games-D_9SiSBT.js";
2
+ import { i as t, o as n } from "./ChatContext-D5rqvt5D.js";
3
+ import { a as r, t as i } from "./connect4Sounds-3a6h7gUC.js";
4
4
  // src/games/tanks/sound/cues.js
5
5
  var a = [
6
6
  "engine_loop",
@@ -206,4 +206,4 @@ function H(e, t) {
206
206
  }
207
207
  export { L as a, R as c, I as i, F as l, V as n, M as o, H as r, z as s, E as t };
208
208
 
209
- //# sourceMappingURL=tanksSounds-DTnidbiq.js.map
209
+ //# sourceMappingURL=tanksSounds-tYPzje61.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tanksSounds-DTnidbiq.js","names":["chatSoundUrl","isSoundEnabled","subscribeSoundPreference","createSoundManager","connect4Sounds","TANKS_LOOPS","play","loopStart","loopStop","IMPACT_SOUNDS","sceneEventToCues","event","name","outcomeToCues","result","TANKS_SOUND_NAMES","LEVELS","DELIVERED_TANKS_SOUNDS","RETRIGGER_MS","LOOP_RAMPS","MP3_LOOP_TRIM","YIELDS_TO","supportsOgg","files","fileFor","extension","configured","format","configureTanksSounds","overrides","available","file","manager","getTanksSoundManager","hidden","canPlay","wantedLoops","syncLoops","allowed","started","startTanksSounds","lastPlayed","suppressedUntil","suppressTanksCue","ms","playTanksCue","key","now","played","startTanksLoop","stopTanksLoop","stopAllTanksLoops","names","runCues","cues","action","handleSceneEvent","suffix","playOutcome"],"sources":["../../src/games/tanks/sound/cues.js","../../src/games/tanks/sound/tanksSounds.js"],"sourcesContent":["// Pure sound choreography for Tanks (no audio, no DOM): maps scene events to\n// one-shot plays and loop start/stop actions.\n\nexport const TANKS_LOOPS = ['engine_loop', 'tracks_loop', 'turret_loop'];\n\nconst play = (name) => ({ name, action: 'play' });\nconst loopStart = (name) => ({ name, action: 'loop_start' });\nconst loopStop = (name) => ({ name, action: 'loop_stop' });\n\nconst IMPACT_SOUNDS = { terrain: 'terrain_impact', obstacle: 'terrain_impact', ground: 'terrain_impact' };\n\n/** Scene onEvent ({ type, ... } or a bare type string) → ordered cues. */\nexport function sceneEventToCues(event) {\n const type = typeof event === 'string' ? event : event?.type;\n switch (type) {\n case 'engine_start': return [play('engine_start')];\n case 'move_start': return [loopStart('engine_loop'), loopStart('tracks_loop')];\n case 'move_end': return [loopStop('tracks_loop'), loopStop('engine_loop')];\n case 'turret_start': return [loopStart('turret_loop')];\n case 'turret_end': return [loopStop('turret_loop')];\n case 'fire': return [play('cannon_fire')];\n case 'projectile_start': return [play('projectile_whistle')];\n case 'impact': {\n const name = IMPACT_SOUNDS[event?.kind];\n return name ? [play(name)] : [];\n }\n case 'tank_hit': return [play('tank_hit')];\n case 'big_explosion': return [play('big_explosion')];\n case 'destroyed': return [play('tank_destroyed')];\n default: return [];\n }\n}\n\n/** 'victory' | 'defeat' → cues; anything else (draw, unknown) stays silent. */\nexport function outcomeToCues(result) {\n if (result === 'victory' || result === 'defeat') return [...TANKS_LOOPS.map(loopStop), play(result)];\n return [];\n}\n","// Tanks (3D) sound layer.\n//\n// Expected files (host serves them from <assets>/sounds/tanks/, i.e. the\n// package's assets/sounds/tanks/), each as .ogg and .mp3: OGG Vorbis is used where\n// the browser supports it, MP3 otherwise. Only names listed in `available` are\n// fetched, so sounds not delivered (the optional UI cues) cost no requests and never error.\n// Format: 44.1 kHz, mono. True peak <= -1 dBTP; loops cut at zero crossings so\n// they repeat without clicks.\n//\n// name duration loudness\n// engine_start 1.5-2.5 s -20 LUFS\n// engine_loop 2-4 s (loop) -24 LUFS\n// tracks_loop 1-2 s (loop) -24 LUFS\n// turret_loop 1-2 s (loop) -26 LUFS\n// cannon_fire 1.5-2.5 s -16 LUFS\n// projectile_whistle 1.5-3 s -22 LUFS\n// terrain_impact 1-2 s -17 LUFS\n// tank_hit 1-2 s -16 LUFS\n// big_explosion 2.5-4 s -14 LUFS\n// tank_destroyed 3-5 s -18 LUFS\n// victory 3-4 s -18 LUFS\n// defeat 2.5-4 s -19 LUFS\n// Tanques LIVE (lives, shield, destructibles): life_lost, respawn, shield_loop (loop),\n// shield_end, health_pickup, crate_break, barrel_explode, wall_hit, last_life\n// optional: turn_start, your_turn, timer_warning (short UI cues, ~-22 LUFS)\n//\n// Gating: the games' shared mute/volume (connect4Sounds) applies through the\n// manager; the GLOBAL chat mute (isSoundEnabled) and a hidden page silence\n// every play and stop all loops.\n\nimport { createSoundManager } from '../../../audio/createSoundManager.js';\nimport { connect4Sounds } from '../../connect4/connect4Sounds.js';\nimport { chatSoundUrl } from '../../../assets/chatAssets.js';\nimport { isSoundEnabled, subscribeSoundPreference } from '../../../notifications/chatSounds.js';\nimport { outcomeToCues, sceneEventToCues, TANKS_LOOPS } from './cues.js';\n\nexport { outcomeToCues, sceneEventToCues } from './cues.js';\n\nexport const TANKS_SOUND_NAMES = [\n 'engine_start', 'engine_loop', 'tracks_loop', 'turret_loop', 'cannon_fire', 'projectile_whistle',\n 'terrain_impact', 'tank_hit', 'big_explosion', 'tank_destroyed', 'victory', 'defeat',\n 'turn_start', 'your_turn', 'timer_warning',\n 'life_lost', 'respawn', 'shield_loop', 'shield_end', 'health_pickup', 'crate_break', 'barrel_explode', 'wall_hit', 'last_life',\n];\n\n// Relative mix, set from the delivered files' measured loudness (blasts ~-16 LUFS,\n// impacts/stingers ~-20, loops -23..-25) so weapons lead and the engine sits under them.\nconst LEVELS = {\n engine_start: 0.6, engine_loop: 0.45, tracks_loop: 0.5, turret_loop: 0.5, cannon_fire: 0.8,\n projectile_whistle: 0.6, terrain_impact: 0.85, tank_hit: 0.85, big_explosion: 0.9, tank_destroyed: 1,\n victory: 0.8, defeat: 0.8, turn_start: 0.35, your_turn: 0.4, timer_warning: 0.35,\n life_lost: 0.8, respawn: 0.7, shield_loop: 0.4, shield_end: 0.55, health_pickup: 0.6, crate_break: 0.75,\n barrel_explode: 0.9, wall_hit: 0.6, last_life: 0.75,\n};\n\n// Sounds shipped in assets/sounds/tanks/ (the UI cues are optional and not delivered).\nexport const DELIVERED_TANKS_SOUNDS = [\n 'engine_start', 'engine_loop', 'tracks_loop', 'turret_loop', 'cannon_fire', 'projectile_whistle',\n 'terrain_impact', 'tank_hit', 'big_explosion', 'tank_destroyed', 'victory', 'defeat',\n 'life_lost', 'respawn', 'shield_loop', 'shield_end', 'health_pickup', 'crate_break', 'barrel_explode', 'wall_hit', 'last_life',\n];\n// The same sound within this window (cluster bomblets, impact + blast in one frame) plays once.\nconst RETRIGGER_MS = 120;\n// Short ramps so loops follow the animation without clicks (defaults are ambient-slow).\nconst LOOP_RAMPS = {\n engine_loop: { fadeIn: 0.5, fadeOut: 0.35 },\n tracks_loop: { fadeIn: 0.12, fadeOut: 0.2 },\n turret_loop: { fadeIn: 0.06, fadeOut: 0.12 },\n shield_loop: { fadeIn: 0.15, fadeOut: 0.25 },\n};\n// MP3 decoders pad both ends; trimming that keeps the Safari loops gapless.\nconst MP3_LOOP_TRIM = 0.03;\n// tank_destroyed is the cinematic destruction sound: big_explosion yields to it.\nconst YIELDS_TO = { tank_destroyed: { name: 'big_explosion', ms: 1500 } };\n\nconst supportsOgg = () => {\n try {\n return typeof Audio !== 'undefined' && new Audio().canPlayType('audio/ogg; codecs=\"vorbis\"') !== '';\n } catch {\n return false;\n }\n};\n\n// Shared by reference with the manager, so configure() still applies to files\n// not fetched yet.\nconst files = {};\nconst fileFor = (name, extension) => `${name}.${String(extension).replace(/^\\./, '')}`;\n\nlet configured = false;\nlet format = 'ogg';\n/**\n * Call before first use to change the format ('auto' | 'ogg' | 'mp3'), the delivered\n * names, or override files ({ name: 'file.ext' | null }).\n */\nexport function configureTanksSounds({ extension = 'auto', available = DELIVERED_TANKS_SOUNDS, files: overrides } = {}) {\n configured = true;\n format = extension === 'auto' ? (supportsOgg() ? 'ogg' : 'mp3') : extension;\n TANKS_SOUND_NAMES.forEach((name) => {\n if (available.includes(name)) files[name] = fileFor(name, format);\n else delete files[name];\n });\n Object.entries(overrides || {}).forEach(([name, file]) => {\n if (file) files[name] = String(file);\n else delete files[name];\n });\n}\n\nlet manager = null;\nexport function getTanksSoundManager() {\n if (!configured) configureTanksSounds();\n if (!manager) {\n manager = createSoundManager({\n id: 'tanks',\n baseUrl: () => chatSoundUrl('tanks'), // resolved lazily at fetch time\n settingsFrom: connect4Sounds, // one mute/volume preference for every chat game\n sounds: files,\n levels: LEVELS,\n });\n }\n return manager;\n}\n\nconst hidden = () => typeof document !== 'undefined' && document.hidden;\nconst canPlay = () => isSoundEnabled() && !hidden();\n\n// Loops the game currently wants; they only sound while canPlay().\nconst wantedLoops = new Set();\nconst syncLoops = () => {\n if (!manager) return;\n const allowed = canPlay();\n new Set([...TANKS_LOOPS, ...wantedLoops]).forEach((name) => {\n if (allowed && wantedLoops.has(name)) manager.startLoop(name, { ...LOOP_RAMPS[name], trim: format === 'mp3' ? MP3_LOOP_TRIM : 0 });\n else manager.stopLoop(name);\n });\n};\n\nlet started = false;\nlet unsubscribePreference = null;\n\n/** Idempotent: starts the manager, preloads files and links mute/visibility. */\nexport function startTanksSounds() {\n const sounds = getTanksSoundManager();\n sounds.preload();\n if (started) return;\n started = true;\n unsubscribePreference = subscribeSoundPreference(syncLoops);\n if (typeof document !== 'undefined') document.addEventListener('visibilitychange', syncLoops);\n}\n\n/** Stops loops and unlinks listeners (the manager restarts on next use). */\nexport function disposeTanksSounds() {\n wantedLoops.clear();\n syncLoops();\n if (unsubscribePreference) { unsubscribePreference(); unsubscribePreference = null; }\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', syncLoops);\n started = false;\n manager?.dispose();\n}\n\nconst lastPlayed = new Map();\nconst suppressedUntil = new Map();\n\n/** Silences `name` for `ms` (e.g. a turn whose destruction already has its own sound). */\nexport function suppressTanksCue(name, ms) {\n suppressedUntil.set(name, Math.max(suppressedUntil.get(name) ?? 0, Date.now() + ms));\n}\n\n/** One-shot cue, deduplicated by key when given. Returns true when scheduled. */\nexport function playTanksCue(name, key) {\n if (!configured) configureTanksSounds();\n if (Date.now() < (suppressedUntil.get(name) ?? 0)) return false;\n if (!canPlay()) {\n // Keep keyed cues from replaying once the page/sound comes back.\n if (key != null) getTanksSoundManager().markPlayed(name, key);\n return false;\n }\n const now = Date.now();\n if (now - (lastPlayed.get(name) ?? 0) < RETRIGGER_MS) return false;\n const played = getTanksSoundManager().play(name, key != null ? { key } : undefined);\n if (played) {\n lastPlayed.set(name, now);\n if (YIELDS_TO[name]) suppressTanksCue(YIELDS_TO[name].name, YIELDS_TO[name].ms);\n }\n return played;\n}\n\nexport function startTanksLoop(name) {\n wantedLoops.add(name);\n getTanksSoundManager();\n syncLoops();\n}\n\nexport function stopTanksLoop(name) {\n wantedLoops.delete(name);\n manager?.stopLoop(name);\n}\n\nexport function stopAllTanksLoops() {\n const names = [...TANKS_LOOPS, ...wantedLoops];\n wantedLoops.clear();\n names.forEach((name) => manager?.stopLoop(name));\n}\n\nconst runCues = (cues, key) => cues.forEach(({ name, action }) => {\n if (action === 'loop_start') startTanksLoop(name);\n else if (action === 'loop_stop') stopTanksLoop(name);\n else playTanksCue(name, key != null ? `${key}:${name}` : undefined);\n});\n\n/** Maps a scene onEvent ({ type, kind?, tankId? }) to sounds. `key` dedupes one-shots (e.g. turn id). */\nexport function handleSceneEvent(event, key) {\n const suffix = event?.tankId != null ? `:${event.tankId}` : '';\n runCues(sceneEventToCues(event), key != null ? `${key}${suffix}` : undefined);\n}\n\n/** 'victory' | 'defeat': stops every loop and plays the outcome once per key. */\nexport function playOutcome(result, key) {\n runCues(outcomeToCues(result), key);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tanksSounds-tYPzje61.js","names":["chatSoundUrl","isSoundEnabled","subscribeSoundPreference","createSoundManager","connect4Sounds","TANKS_LOOPS","play","loopStart","loopStop","IMPACT_SOUNDS","sceneEventToCues","event","name","outcomeToCues","result","TANKS_SOUND_NAMES","LEVELS","DELIVERED_TANKS_SOUNDS","RETRIGGER_MS","LOOP_RAMPS","MP3_LOOP_TRIM","YIELDS_TO","supportsOgg","files","fileFor","extension","configured","format","configureTanksSounds","overrides","available","file","manager","getTanksSoundManager","hidden","canPlay","wantedLoops","syncLoops","allowed","started","startTanksSounds","lastPlayed","suppressedUntil","suppressTanksCue","ms","playTanksCue","key","now","played","startTanksLoop","stopTanksLoop","stopAllTanksLoops","names","runCues","cues","action","handleSceneEvent","suffix","playOutcome"],"sources":["../../src/games/tanks/sound/cues.js","../../src/games/tanks/sound/tanksSounds.js"],"sourcesContent":["// Pure sound choreography for Tanks (no audio, no DOM): maps scene events to\n// one-shot plays and loop start/stop actions.\n\nexport const TANKS_LOOPS = ['engine_loop', 'tracks_loop', 'turret_loop'];\n\nconst play = (name) => ({ name, action: 'play' });\nconst loopStart = (name) => ({ name, action: 'loop_start' });\nconst loopStop = (name) => ({ name, action: 'loop_stop' });\n\nconst IMPACT_SOUNDS = { terrain: 'terrain_impact', obstacle: 'terrain_impact', ground: 'terrain_impact' };\n\n/** Scene onEvent ({ type, ... } or a bare type string) → ordered cues. */\nexport function sceneEventToCues(event) {\n const type = typeof event === 'string' ? event : event?.type;\n switch (type) {\n case 'engine_start': return [play('engine_start')];\n case 'move_start': return [loopStart('engine_loop'), loopStart('tracks_loop')];\n case 'move_end': return [loopStop('tracks_loop'), loopStop('engine_loop')];\n case 'turret_start': return [loopStart('turret_loop')];\n case 'turret_end': return [loopStop('turret_loop')];\n case 'fire': return [play('cannon_fire')];\n case 'projectile_start': return [play('projectile_whistle')];\n case 'impact': {\n const name = IMPACT_SOUNDS[event?.kind];\n return name ? [play(name)] : [];\n }\n case 'tank_hit': return [play('tank_hit')];\n case 'big_explosion': return [play('big_explosion')];\n case 'destroyed': return [play('tank_destroyed')];\n default: return [];\n }\n}\n\n/** 'victory' | 'defeat' → cues; anything else (draw, unknown) stays silent. */\nexport function outcomeToCues(result) {\n if (result === 'victory' || result === 'defeat') return [...TANKS_LOOPS.map(loopStop), play(result)];\n return [];\n}\n","// Tanks (3D) sound layer.\n//\n// Expected files (host serves them from <assets>/sounds/tanks/, i.e. the\n// package's assets/sounds/tanks/), each as .ogg and .mp3: OGG Vorbis is used where\n// the browser supports it, MP3 otherwise. Only names listed in `available` are\n// fetched, so sounds not delivered (the optional UI cues) cost no requests and never error.\n// Format: 44.1 kHz, mono. True peak <= -1 dBTP; loops cut at zero crossings so\n// they repeat without clicks.\n//\n// name duration loudness\n// engine_start 1.5-2.5 s -20 LUFS\n// engine_loop 2-4 s (loop) -24 LUFS\n// tracks_loop 1-2 s (loop) -24 LUFS\n// turret_loop 1-2 s (loop) -26 LUFS\n// cannon_fire 1.5-2.5 s -16 LUFS\n// projectile_whistle 1.5-3 s -22 LUFS\n// terrain_impact 1-2 s -17 LUFS\n// tank_hit 1-2 s -16 LUFS\n// big_explosion 2.5-4 s -14 LUFS\n// tank_destroyed 3-5 s -18 LUFS\n// victory 3-4 s -18 LUFS\n// defeat 2.5-4 s -19 LUFS\n// Tanques LIVE (lives, shield, destructibles): life_lost, respawn, shield_loop (loop),\n// shield_end, health_pickup, crate_break, barrel_explode, wall_hit, last_life\n// optional: turn_start, your_turn, timer_warning (short UI cues, ~-22 LUFS)\n//\n// Gating: the games' shared mute/volume (connect4Sounds) applies through the\n// manager; the GLOBAL chat mute (isSoundEnabled) and a hidden page silence\n// every play and stop all loops.\n\nimport { createSoundManager } from '../../../audio/createSoundManager.js';\nimport { connect4Sounds } from '../../connect4/connect4Sounds.js';\nimport { chatSoundUrl } from '../../../assets/chatAssets.js';\nimport { isSoundEnabled, subscribeSoundPreference } from '../../../notifications/chatSounds.js';\nimport { outcomeToCues, sceneEventToCues, TANKS_LOOPS } from './cues.js';\n\nexport { outcomeToCues, sceneEventToCues } from './cues.js';\n\nexport const TANKS_SOUND_NAMES = [\n 'engine_start', 'engine_loop', 'tracks_loop', 'turret_loop', 'cannon_fire', 'projectile_whistle',\n 'terrain_impact', 'tank_hit', 'big_explosion', 'tank_destroyed', 'victory', 'defeat',\n 'turn_start', 'your_turn', 'timer_warning',\n 'life_lost', 'respawn', 'shield_loop', 'shield_end', 'health_pickup', 'crate_break', 'barrel_explode', 'wall_hit', 'last_life',\n];\n\n// Relative mix, set from the delivered files' measured loudness (blasts ~-16 LUFS,\n// impacts/stingers ~-20, loops -23..-25) so weapons lead and the engine sits under them.\nconst LEVELS = {\n engine_start: 0.6, engine_loop: 0.45, tracks_loop: 0.5, turret_loop: 0.5, cannon_fire: 0.8,\n projectile_whistle: 0.6, terrain_impact: 0.85, tank_hit: 0.85, big_explosion: 0.9, tank_destroyed: 1,\n victory: 0.8, defeat: 0.8, turn_start: 0.35, your_turn: 0.4, timer_warning: 0.35,\n life_lost: 0.8, respawn: 0.7, shield_loop: 0.4, shield_end: 0.55, health_pickup: 0.6, crate_break: 0.75,\n barrel_explode: 0.9, wall_hit: 0.6, last_life: 0.75,\n};\n\n// Sounds shipped in assets/sounds/tanks/ (the UI cues are optional and not delivered).\nexport const DELIVERED_TANKS_SOUNDS = [\n 'engine_start', 'engine_loop', 'tracks_loop', 'turret_loop', 'cannon_fire', 'projectile_whistle',\n 'terrain_impact', 'tank_hit', 'big_explosion', 'tank_destroyed', 'victory', 'defeat',\n 'life_lost', 'respawn', 'shield_loop', 'shield_end', 'health_pickup', 'crate_break', 'barrel_explode', 'wall_hit', 'last_life',\n];\n// The same sound within this window (cluster bomblets, impact + blast in one frame) plays once.\nconst RETRIGGER_MS = 120;\n// Short ramps so loops follow the animation without clicks (defaults are ambient-slow).\nconst LOOP_RAMPS = {\n engine_loop: { fadeIn: 0.5, fadeOut: 0.35 },\n tracks_loop: { fadeIn: 0.12, fadeOut: 0.2 },\n turret_loop: { fadeIn: 0.06, fadeOut: 0.12 },\n shield_loop: { fadeIn: 0.15, fadeOut: 0.25 },\n};\n// MP3 decoders pad both ends; trimming that keeps the Safari loops gapless.\nconst MP3_LOOP_TRIM = 0.03;\n// tank_destroyed is the cinematic destruction sound: big_explosion yields to it.\nconst YIELDS_TO = { tank_destroyed: { name: 'big_explosion', ms: 1500 } };\n\nconst supportsOgg = () => {\n try {\n return typeof Audio !== 'undefined' && new Audio().canPlayType('audio/ogg; codecs=\"vorbis\"') !== '';\n } catch {\n return false;\n }\n};\n\n// Shared by reference with the manager, so configure() still applies to files\n// not fetched yet.\nconst files = {};\nconst fileFor = (name, extension) => `${name}.${String(extension).replace(/^\\./, '')}`;\n\nlet configured = false;\nlet format = 'ogg';\n/**\n * Call before first use to change the format ('auto' | 'ogg' | 'mp3'), the delivered\n * names, or override files ({ name: 'file.ext' | null }).\n */\nexport function configureTanksSounds({ extension = 'auto', available = DELIVERED_TANKS_SOUNDS, files: overrides } = {}) {\n configured = true;\n format = extension === 'auto' ? (supportsOgg() ? 'ogg' : 'mp3') : extension;\n TANKS_SOUND_NAMES.forEach((name) => {\n if (available.includes(name)) files[name] = fileFor(name, format);\n else delete files[name];\n });\n Object.entries(overrides || {}).forEach(([name, file]) => {\n if (file) files[name] = String(file);\n else delete files[name];\n });\n}\n\nlet manager = null;\nexport function getTanksSoundManager() {\n if (!configured) configureTanksSounds();\n if (!manager) {\n manager = createSoundManager({\n id: 'tanks',\n baseUrl: () => chatSoundUrl('tanks'), // resolved lazily at fetch time\n settingsFrom: connect4Sounds, // one mute/volume preference for every chat game\n sounds: files,\n levels: LEVELS,\n });\n }\n return manager;\n}\n\nconst hidden = () => typeof document !== 'undefined' && document.hidden;\nconst canPlay = () => isSoundEnabled() && !hidden();\n\n// Loops the game currently wants; they only sound while canPlay().\nconst wantedLoops = new Set();\nconst syncLoops = () => {\n if (!manager) return;\n const allowed = canPlay();\n new Set([...TANKS_LOOPS, ...wantedLoops]).forEach((name) => {\n if (allowed && wantedLoops.has(name)) manager.startLoop(name, { ...LOOP_RAMPS[name], trim: format === 'mp3' ? MP3_LOOP_TRIM : 0 });\n else manager.stopLoop(name);\n });\n};\n\nlet started = false;\nlet unsubscribePreference = null;\n\n/** Idempotent: starts the manager, preloads files and links mute/visibility. */\nexport function startTanksSounds() {\n const sounds = getTanksSoundManager();\n sounds.preload();\n if (started) return;\n started = true;\n unsubscribePreference = subscribeSoundPreference(syncLoops);\n if (typeof document !== 'undefined') document.addEventListener('visibilitychange', syncLoops);\n}\n\n/** Stops loops and unlinks listeners (the manager restarts on next use). */\nexport function disposeTanksSounds() {\n wantedLoops.clear();\n syncLoops();\n if (unsubscribePreference) { unsubscribePreference(); unsubscribePreference = null; }\n if (typeof document !== 'undefined') document.removeEventListener('visibilitychange', syncLoops);\n started = false;\n manager?.dispose();\n}\n\nconst lastPlayed = new Map();\nconst suppressedUntil = new Map();\n\n/** Silences `name` for `ms` (e.g. a turn whose destruction already has its own sound). */\nexport function suppressTanksCue(name, ms) {\n suppressedUntil.set(name, Math.max(suppressedUntil.get(name) ?? 0, Date.now() + ms));\n}\n\n/** One-shot cue, deduplicated by key when given. Returns true when scheduled. */\nexport function playTanksCue(name, key) {\n if (!configured) configureTanksSounds();\n if (Date.now() < (suppressedUntil.get(name) ?? 0)) return false;\n if (!canPlay()) {\n // Keep keyed cues from replaying once the page/sound comes back.\n if (key != null) getTanksSoundManager().markPlayed(name, key);\n return false;\n }\n const now = Date.now();\n if (now - (lastPlayed.get(name) ?? 0) < RETRIGGER_MS) return false;\n const played = getTanksSoundManager().play(name, key != null ? { key } : undefined);\n if (played) {\n lastPlayed.set(name, now);\n if (YIELDS_TO[name]) suppressTanksCue(YIELDS_TO[name].name, YIELDS_TO[name].ms);\n }\n return played;\n}\n\nexport function startTanksLoop(name) {\n wantedLoops.add(name);\n getTanksSoundManager();\n syncLoops();\n}\n\nexport function stopTanksLoop(name) {\n wantedLoops.delete(name);\n manager?.stopLoop(name);\n}\n\nexport function stopAllTanksLoops() {\n const names = [...TANKS_LOOPS, ...wantedLoops];\n wantedLoops.clear();\n names.forEach((name) => manager?.stopLoop(name));\n}\n\nconst runCues = (cues, key) => cues.forEach(({ name, action }) => {\n if (action === 'loop_start') startTanksLoop(name);\n else if (action === 'loop_stop') stopTanksLoop(name);\n else playTanksCue(name, key != null ? `${key}:${name}` : undefined);\n});\n\n/** Maps a scene onEvent ({ type, kind?, tankId? }) to sounds. `key` dedupes one-shots (e.g. turn id). */\nexport function handleSceneEvent(event, key) {\n const suffix = event?.tankId != null ? `:${event.tankId}` : '';\n runCues(sceneEventToCues(event), key != null ? `${key}${suffix}` : undefined);\n}\n\n/** 'victory' | 'defeat': stops every loop and plays the outcome once per key. */\nexport function playOutcome(result, key) {\n runCues(outcomeToCues(result), key);\n}\n"],"mappings":"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"}