hbengine 0.2.3

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 (561) hide show
  1. package/README.md +20 -0
  2. package/engine-cli/connect.mjs +73 -0
  3. package/engine-cli/hbengine.mjs +565 -0
  4. package/engine-cli/package.json +1 -0
  5. package/engine-cli/templates.mjs +14 -0
  6. package/engine-conformance/analyse-worker.mjs +12 -0
  7. package/engine-conformance/analyse.mjs +4458 -0
  8. package/engine-conformance/bundle.mjs +208 -0
  9. package/engine-conformance/clock.mjs +294 -0
  10. package/engine-conformance/csp.go +56 -0
  11. package/engine-conformance/csp.json +6 -0
  12. package/engine-conformance/csp.mjs +20 -0
  13. package/engine-conformance/gzip-sizes.mjs +9 -0
  14. package/engine-conformance/package.json +1 -0
  15. package/engine-conformance/picture.mjs +55 -0
  16. package/engine-conformance/report.go +349 -0
  17. package/engine-conformance/run.mjs +1217 -0
  18. package/engine-conformance/scan.mjs +476 -0
  19. package/engine-conformance/testdata/clock-overlay/bundle/assets.json +1 -0
  20. package/engine-conformance/testdata/clock-overlay/bundle/game.css +17 -0
  21. package/engine-conformance/testdata/clock-overlay/bundle/game.js +83 -0
  22. package/engine-conformance/testdata/clock-overlay/bundle/hbtest.json +4 -0
  23. package/engine-conformance/testdata/clock-overlay/bundle/index.html +34 -0
  24. package/engine-conformance/testdata/clock-probe/bundle/assets.json +1 -0
  25. package/engine-conformance/testdata/clock-probe/bundle/game.css +17 -0
  26. package/engine-conformance/testdata/clock-probe/bundle/game.js +65 -0
  27. package/engine-conformance/testdata/clock-probe/bundle/hbtest.json +4 -0
  28. package/engine-conformance/testdata/clock-probe/bundle/index.html +35 -0
  29. package/engine-conformance/testdata/clock-probe/bundle/probe-worker.js +46 -0
  30. package/engine-conformance/testdata/clock-probe/bundle/probe.js +210 -0
  31. package/engine-conformance/testdata/clock-probe/math/dice.tsv +100 -0
  32. package/engine-conformance/testdata/clock-probe/math/manifest.json +2925 -0
  33. package/engine-conformance/testdata/clock-worker/bundle/assets.json +1 -0
  34. package/engine-conformance/testdata/clock-worker/bundle/clock-worker.js +14 -0
  35. package/engine-conformance/testdata/clock-worker/bundle/game.css +17 -0
  36. package/engine-conformance/testdata/clock-worker/bundle/game.js +61 -0
  37. package/engine-conformance/testdata/clock-worker/bundle/hbtest.json +4 -0
  38. package/engine-conformance/testdata/clock-worker/bundle/index.html +35 -0
  39. package/engine-conformance/testdata/clock-worker/math/dice.tsv +100 -0
  40. package/engine-conformance/testdata/clock-worker/math/manifest.json +2925 -0
  41. package/engine-conformance/travel.mjs +1209 -0
  42. package/engine-conformance/vendor/acorn/LICENSE +21 -0
  43. package/engine-conformance/vendor/acorn/acorn.mjs +6266 -0
  44. package/engine-conformance/wasm-releases.json +168 -0
  45. package/engine-examples/README.md +23 -0
  46. package/engine-examples/coin/AGENTS.md +3 -0
  47. package/engine-examples/coin/README.md +7 -0
  48. package/engine-examples/coin/bundle/assets.json +1 -0
  49. package/engine-examples/coin/bundle/game.css +5 -0
  50. package/engine-examples/coin/bundle/game.js +19 -0
  51. package/engine-examples/coin/bundle/hbtest.json +5 -0
  52. package/engine-examples/coin/bundle/index.html +16 -0
  53. package/engine-examples/coin/math/coin.tsv +2 -0
  54. package/engine-examples/coin/math/manifest.json +17 -0
  55. package/engine-examples/crash/AGENTS.md +3 -0
  56. package/engine-examples/crash/README.md +14 -0
  57. package/engine-examples/crash/bundle/assets.json +1 -0
  58. package/engine-examples/crash/bundle/game.css +8 -0
  59. package/engine-examples/crash/bundle/game.js +90 -0
  60. package/engine-examples/crash/bundle/hbtest.json +5 -0
  61. package/engine-examples/crash/bundle/index.html +20 -0
  62. package/engine-examples/crash/math/crash-01.tsv +2 -0
  63. package/engine-examples/crash/math/crash-02.tsv +2 -0
  64. package/engine-examples/crash/math/crash-03.tsv +2 -0
  65. package/engine-examples/crash/math/crash-04.tsv +2 -0
  66. package/engine-examples/crash/math/crash-05.tsv +2 -0
  67. package/engine-examples/crash/math/crash-06.tsv +2 -0
  68. package/engine-examples/crash/math/crash-07.tsv +2 -0
  69. package/engine-examples/crash/math/crash-08.tsv +2 -0
  70. package/engine-examples/crash/math/crash-09.tsv +2 -0
  71. package/engine-examples/crash/math/crash-10.tsv +2 -0
  72. package/engine-examples/crash/math/crash-11.tsv +2 -0
  73. package/engine-examples/crash/math/crash-12.tsv +2 -0
  74. package/engine-examples/crash/math/crash-13.tsv +2 -0
  75. package/engine-examples/crash/math/crash-14.tsv +2 -0
  76. package/engine-examples/crash/math/crash-15.tsv +2 -0
  77. package/engine-examples/crash/math/crash-16.tsv +2 -0
  78. package/engine-examples/crash/math/crash-17.tsv +2 -0
  79. package/engine-examples/crash/math/crash-18.tsv +2 -0
  80. package/engine-examples/crash/math/crash-19.tsv +2 -0
  81. package/engine-examples/crash/math/crash-20.tsv +2 -0
  82. package/engine-examples/crash/math/crash-21.tsv +2 -0
  83. package/engine-examples/crash/math/crash-22.tsv +2 -0
  84. package/engine-examples/crash/math/crash-23.tsv +2 -0
  85. package/engine-examples/crash/math/crash-24.tsv +2 -0
  86. package/engine-examples/crash/math/crash-25.tsv +2 -0
  87. package/engine-examples/crash/math/crash-26.tsv +2 -0
  88. package/engine-examples/crash/math/crash-27.tsv +2 -0
  89. package/engine-examples/crash/math/crash-28.tsv +2 -0
  90. package/engine-examples/crash/math/crash-29.tsv +2 -0
  91. package/engine-examples/crash/math/crash-30.tsv +2 -0
  92. package/engine-examples/crash/math/crash-31.tsv +2 -0
  93. package/engine-examples/crash/math/crash-32.tsv +2 -0
  94. package/engine-examples/crash/math/crash-33.tsv +2 -0
  95. package/engine-examples/crash/math/crash-34.tsv +2 -0
  96. package/engine-examples/crash/math/crash-35.tsv +2 -0
  97. package/engine-examples/crash/math/crash-36.tsv +2 -0
  98. package/engine-examples/crash/math/crash-37.tsv +2 -0
  99. package/engine-examples/crash/math/crash-38.tsv +2 -0
  100. package/engine-examples/crash/math/crash-39.tsv +2 -0
  101. package/engine-examples/crash/math/crash-40.tsv +2 -0
  102. package/engine-examples/crash/math/crash-41.tsv +2 -0
  103. package/engine-examples/crash/math/crash-42.tsv +2 -0
  104. package/engine-examples/crash/math/crash-43.tsv +2 -0
  105. package/engine-examples/crash/math/crash-44.tsv +2 -0
  106. package/engine-examples/crash/math/crash-45.tsv +2 -0
  107. package/engine-examples/crash/math/crash-46.tsv +2 -0
  108. package/engine-examples/crash/math/crash-47.tsv +2 -0
  109. package/engine-examples/crash/math/crash-48.tsv +2 -0
  110. package/engine-examples/crash/math/crash-49.tsv +2 -0
  111. package/engine-examples/crash/math/crash-50.tsv +2 -0
  112. package/engine-examples/crash/math/crash-51.tsv +2 -0
  113. package/engine-examples/crash/math/crash-52.tsv +2 -0
  114. package/engine-examples/crash/math/crash-53.tsv +2 -0
  115. package/engine-examples/crash/math/crash-54.tsv +2 -0
  116. package/engine-examples/crash/math/crash-55.tsv +2 -0
  117. package/engine-examples/crash/math/crash-56.tsv +2 -0
  118. package/engine-examples/crash/math/crash-57.tsv +2 -0
  119. package/engine-examples/crash/math/crash-58.tsv +2 -0
  120. package/engine-examples/crash/math/crash-59.tsv +2 -0
  121. package/engine-examples/crash/math/crash-60.tsv +2 -0
  122. package/engine-examples/crash/math/crash-61.tsv +2 -0
  123. package/engine-examples/crash/math/crash-62.tsv +2 -0
  124. package/engine-examples/crash/math/crash-63.tsv +2 -0
  125. package/engine-examples/crash/math/crash-64.tsv +2 -0
  126. package/engine-examples/crash/math/gen.mjs +31 -0
  127. package/engine-examples/crash/math/manifest.json +1 -0
  128. package/engine-examples/dice/AGENTS.md +3 -0
  129. package/engine-examples/dice/README.md +25 -0
  130. package/engine-examples/dice/bundle/assets.json +1 -0
  131. package/engine-examples/dice/bundle/game.css +17 -0
  132. package/engine-examples/dice/bundle/game.js +61 -0
  133. package/engine-examples/dice/bundle/hbtest.json +4 -0
  134. package/engine-examples/dice/bundle/index.html +34 -0
  135. package/engine-examples/dice/math/dice.tsv +100 -0
  136. package/engine-examples/dice/math/manifest.json +2925 -0
  137. package/engine-examples/keno/AGENTS.md +3 -0
  138. package/engine-examples/keno/README.md +11 -0
  139. package/engine-examples/keno/bundle/assets.json +1 -0
  140. package/engine-examples/keno/bundle/game.css +14 -0
  141. package/engine-examples/keno/bundle/game.js +86 -0
  142. package/engine-examples/keno/bundle/hbtest.json +4 -0
  143. package/engine-examples/keno/bundle/i18n.json +342 -0
  144. package/engine-examples/keno/bundle/index.html +20 -0
  145. package/engine-examples/keno/bundle/model.json +1 -0
  146. package/engine-examples/keno/math/gen.mjs +35 -0
  147. package/engine-examples/keno/math/keno-picks-1.tsv +2 -0
  148. package/engine-examples/keno/math/keno-picks-10.tsv +11 -0
  149. package/engine-examples/keno/math/keno-picks-2.tsv +3 -0
  150. package/engine-examples/keno/math/keno-picks-3.tsv +4 -0
  151. package/engine-examples/keno/math/keno-picks-4.tsv +5 -0
  152. package/engine-examples/keno/math/keno-picks-5.tsv +6 -0
  153. package/engine-examples/keno/math/keno-picks-6.tsv +7 -0
  154. package/engine-examples/keno/math/keno-picks-7.tsv +8 -0
  155. package/engine-examples/keno/math/keno-picks-8.tsv +9 -0
  156. package/engine-examples/keno/math/keno-picks-9.tsv +10 -0
  157. package/engine-examples/keno/math/manifest.json +1 -0
  158. package/engine-examples/keno/math-kit/pack.mjs +165 -0
  159. package/engine-examples/keno/math-kit/package.json +8 -0
  160. package/engine-examples/keno/math-kit/slot.mjs +94 -0
  161. package/engine-examples/keno/math-kit/tumble.mjs +94 -0
  162. package/engine-examples/lines/AGENTS.md +3 -0
  163. package/engine-examples/lines/README.md +20 -0
  164. package/engine-examples/lines/bundle/assets.json +1 -0
  165. package/engine-examples/lines/bundle/game.css +16 -0
  166. package/engine-examples/lines/bundle/game.js +125 -0
  167. package/engine-examples/lines/bundle/hbtest.json +4 -0
  168. package/engine-examples/lines/bundle/i18n.json +242 -0
  169. package/engine-examples/lines/bundle/index.html +23 -0
  170. package/engine-examples/lines/bundle/model.json +1 -0
  171. package/engine-examples/lines/math/gen.mjs +54 -0
  172. package/engine-examples/lines/math-kit/pack.mjs +165 -0
  173. package/engine-examples/lines/math-kit/package.json +8 -0
  174. package/engine-examples/lines/math-kit/slot.mjs +94 -0
  175. package/engine-examples/lines/math-kit/tumble.mjs +94 -0
  176. package/engine-examples/mines/AGENTS.md +3 -0
  177. package/engine-examples/mines/README.md +13 -0
  178. package/engine-examples/mines/bundle/assets.json +1 -0
  179. package/engine-examples/mines/bundle/game.css +12 -0
  180. package/engine-examples/mines/bundle/game.js +58 -0
  181. package/engine-examples/mines/bundle/hbtest.json +8 -0
  182. package/engine-examples/mines/bundle/index.html +16 -0
  183. package/engine-examples/mines/math/gen.mjs +40 -0
  184. package/engine-examples/mines/math/manifest.json +1 -0
  185. package/engine-examples/mines/math/mines-1-1.tsv +2 -0
  186. package/engine-examples/mines/math/mines-1-2.tsv +2 -0
  187. package/engine-examples/mines/math/mines-1-22.tsv +2 -0
  188. package/engine-examples/mines/math/mines-1-23.tsv +2 -0
  189. package/engine-examples/mines/math/mines-1-24.tsv +2 -0
  190. package/engine-examples/mines/math/mines-1-3.tsv +2 -0
  191. package/engine-examples/mines/math/mines-10-1.tsv +2 -0
  192. package/engine-examples/mines/math/mines-10-10.tsv +2 -0
  193. package/engine-examples/mines/math/mines-10-11.tsv +2 -0
  194. package/engine-examples/mines/math/mines-10-12.tsv +2 -0
  195. package/engine-examples/mines/math/mines-10-13.tsv +2 -0
  196. package/engine-examples/mines/math/mines-10-14.tsv +2 -0
  197. package/engine-examples/mines/math/mines-10-15.tsv +2 -0
  198. package/engine-examples/mines/math/mines-10-2.tsv +2 -0
  199. package/engine-examples/mines/math/mines-10-3.tsv +2 -0
  200. package/engine-examples/mines/math/mines-10-4.tsv +2 -0
  201. package/engine-examples/mines/math/mines-10-5.tsv +2 -0
  202. package/engine-examples/mines/math/mines-10-6.tsv +2 -0
  203. package/engine-examples/mines/math/mines-10-7.tsv +2 -0
  204. package/engine-examples/mines/math/mines-10-8.tsv +2 -0
  205. package/engine-examples/mines/math/mines-10-9.tsv +2 -0
  206. package/engine-examples/mines/math/mines-11-1.tsv +2 -0
  207. package/engine-examples/mines/math/mines-11-10.tsv +2 -0
  208. package/engine-examples/mines/math/mines-11-11.tsv +2 -0
  209. package/engine-examples/mines/math/mines-11-12.tsv +2 -0
  210. package/engine-examples/mines/math/mines-11-13.tsv +2 -0
  211. package/engine-examples/mines/math/mines-11-14.tsv +2 -0
  212. package/engine-examples/mines/math/mines-11-2.tsv +2 -0
  213. package/engine-examples/mines/math/mines-11-3.tsv +2 -0
  214. package/engine-examples/mines/math/mines-11-4.tsv +2 -0
  215. package/engine-examples/mines/math/mines-11-5.tsv +2 -0
  216. package/engine-examples/mines/math/mines-11-6.tsv +2 -0
  217. package/engine-examples/mines/math/mines-11-7.tsv +2 -0
  218. package/engine-examples/mines/math/mines-11-8.tsv +2 -0
  219. package/engine-examples/mines/math/mines-11-9.tsv +2 -0
  220. package/engine-examples/mines/math/mines-12-1.tsv +2 -0
  221. package/engine-examples/mines/math/mines-12-10.tsv +2 -0
  222. package/engine-examples/mines/math/mines-12-11.tsv +2 -0
  223. package/engine-examples/mines/math/mines-12-12.tsv +2 -0
  224. package/engine-examples/mines/math/mines-12-13.tsv +2 -0
  225. package/engine-examples/mines/math/mines-12-2.tsv +2 -0
  226. package/engine-examples/mines/math/mines-12-3.tsv +2 -0
  227. package/engine-examples/mines/math/mines-12-4.tsv +2 -0
  228. package/engine-examples/mines/math/mines-12-5.tsv +2 -0
  229. package/engine-examples/mines/math/mines-12-6.tsv +2 -0
  230. package/engine-examples/mines/math/mines-12-7.tsv +2 -0
  231. package/engine-examples/mines/math/mines-12-8.tsv +2 -0
  232. package/engine-examples/mines/math/mines-12-9.tsv +2 -0
  233. package/engine-examples/mines/math/mines-13-1.tsv +2 -0
  234. package/engine-examples/mines/math/mines-13-10.tsv +2 -0
  235. package/engine-examples/mines/math/mines-13-11.tsv +2 -0
  236. package/engine-examples/mines/math/mines-13-12.tsv +2 -0
  237. package/engine-examples/mines/math/mines-13-2.tsv +2 -0
  238. package/engine-examples/mines/math/mines-13-3.tsv +2 -0
  239. package/engine-examples/mines/math/mines-13-4.tsv +2 -0
  240. package/engine-examples/mines/math/mines-13-5.tsv +2 -0
  241. package/engine-examples/mines/math/mines-13-6.tsv +2 -0
  242. package/engine-examples/mines/math/mines-13-7.tsv +2 -0
  243. package/engine-examples/mines/math/mines-13-8.tsv +2 -0
  244. package/engine-examples/mines/math/mines-13-9.tsv +2 -0
  245. package/engine-examples/mines/math/mines-14-1.tsv +2 -0
  246. package/engine-examples/mines/math/mines-14-10.tsv +2 -0
  247. package/engine-examples/mines/math/mines-14-11.tsv +2 -0
  248. package/engine-examples/mines/math/mines-14-2.tsv +2 -0
  249. package/engine-examples/mines/math/mines-14-3.tsv +2 -0
  250. package/engine-examples/mines/math/mines-14-4.tsv +2 -0
  251. package/engine-examples/mines/math/mines-14-5.tsv +2 -0
  252. package/engine-examples/mines/math/mines-14-6.tsv +2 -0
  253. package/engine-examples/mines/math/mines-14-7.tsv +2 -0
  254. package/engine-examples/mines/math/mines-14-8.tsv +2 -0
  255. package/engine-examples/mines/math/mines-14-9.tsv +2 -0
  256. package/engine-examples/mines/math/mines-15-1.tsv +2 -0
  257. package/engine-examples/mines/math/mines-15-10.tsv +2 -0
  258. package/engine-examples/mines/math/mines-15-2.tsv +2 -0
  259. package/engine-examples/mines/math/mines-15-3.tsv +2 -0
  260. package/engine-examples/mines/math/mines-15-4.tsv +2 -0
  261. package/engine-examples/mines/math/mines-15-5.tsv +2 -0
  262. package/engine-examples/mines/math/mines-15-6.tsv +2 -0
  263. package/engine-examples/mines/math/mines-15-7.tsv +2 -0
  264. package/engine-examples/mines/math/mines-15-8.tsv +2 -0
  265. package/engine-examples/mines/math/mines-15-9.tsv +2 -0
  266. package/engine-examples/mines/math/mines-16-1.tsv +2 -0
  267. package/engine-examples/mines/math/mines-16-2.tsv +2 -0
  268. package/engine-examples/mines/math/mines-16-3.tsv +2 -0
  269. package/engine-examples/mines/math/mines-16-4.tsv +2 -0
  270. package/engine-examples/mines/math/mines-16-5.tsv +2 -0
  271. package/engine-examples/mines/math/mines-16-6.tsv +2 -0
  272. package/engine-examples/mines/math/mines-16-7.tsv +2 -0
  273. package/engine-examples/mines/math/mines-16-8.tsv +2 -0
  274. package/engine-examples/mines/math/mines-16-9.tsv +2 -0
  275. package/engine-examples/mines/math/mines-17-1.tsv +2 -0
  276. package/engine-examples/mines/math/mines-17-2.tsv +2 -0
  277. package/engine-examples/mines/math/mines-17-3.tsv +2 -0
  278. package/engine-examples/mines/math/mines-17-4.tsv +2 -0
  279. package/engine-examples/mines/math/mines-17-5.tsv +2 -0
  280. package/engine-examples/mines/math/mines-17-6.tsv +2 -0
  281. package/engine-examples/mines/math/mines-17-7.tsv +2 -0
  282. package/engine-examples/mines/math/mines-17-8.tsv +2 -0
  283. package/engine-examples/mines/math/mines-18-1.tsv +2 -0
  284. package/engine-examples/mines/math/mines-18-2.tsv +2 -0
  285. package/engine-examples/mines/math/mines-18-3.tsv +2 -0
  286. package/engine-examples/mines/math/mines-18-4.tsv +2 -0
  287. package/engine-examples/mines/math/mines-18-5.tsv +2 -0
  288. package/engine-examples/mines/math/mines-18-6.tsv +2 -0
  289. package/engine-examples/mines/math/mines-18-7.tsv +2 -0
  290. package/engine-examples/mines/math/mines-19-1.tsv +2 -0
  291. package/engine-examples/mines/math/mines-19-2.tsv +2 -0
  292. package/engine-examples/mines/math/mines-19-3.tsv +2 -0
  293. package/engine-examples/mines/math/mines-19-4.tsv +2 -0
  294. package/engine-examples/mines/math/mines-19-5.tsv +2 -0
  295. package/engine-examples/mines/math/mines-19-6.tsv +2 -0
  296. package/engine-examples/mines/math/mines-2-1.tsv +2 -0
  297. package/engine-examples/mines/math/mines-2-19.tsv +2 -0
  298. package/engine-examples/mines/math/mines-2-2.tsv +2 -0
  299. package/engine-examples/mines/math/mines-2-20.tsv +2 -0
  300. package/engine-examples/mines/math/mines-2-21.tsv +2 -0
  301. package/engine-examples/mines/math/mines-2-22.tsv +2 -0
  302. package/engine-examples/mines/math/mines-2-23.tsv +2 -0
  303. package/engine-examples/mines/math/mines-2-3.tsv +2 -0
  304. package/engine-examples/mines/math/mines-2-4.tsv +2 -0
  305. package/engine-examples/mines/math/mines-2-5.tsv +2 -0
  306. package/engine-examples/mines/math/mines-20-1.tsv +2 -0
  307. package/engine-examples/mines/math/mines-20-2.tsv +2 -0
  308. package/engine-examples/mines/math/mines-20-3.tsv +2 -0
  309. package/engine-examples/mines/math/mines-20-4.tsv +2 -0
  310. package/engine-examples/mines/math/mines-20-5.tsv +2 -0
  311. package/engine-examples/mines/math/mines-21-1.tsv +2 -0
  312. package/engine-examples/mines/math/mines-21-2.tsv +2 -0
  313. package/engine-examples/mines/math/mines-21-3.tsv +2 -0
  314. package/engine-examples/mines/math/mines-21-4.tsv +2 -0
  315. package/engine-examples/mines/math/mines-22-1.tsv +2 -0
  316. package/engine-examples/mines/math/mines-22-2.tsv +2 -0
  317. package/engine-examples/mines/math/mines-22-3.tsv +2 -0
  318. package/engine-examples/mines/math/mines-23-1.tsv +2 -0
  319. package/engine-examples/mines/math/mines-23-2.tsv +2 -0
  320. package/engine-examples/mines/math/mines-24-1.tsv +2 -0
  321. package/engine-examples/mines/math/mines-3-1.tsv +2 -0
  322. package/engine-examples/mines/math/mines-3-17.tsv +2 -0
  323. package/engine-examples/mines/math/mines-3-18.tsv +2 -0
  324. package/engine-examples/mines/math/mines-3-19.tsv +2 -0
  325. package/engine-examples/mines/math/mines-3-2.tsv +2 -0
  326. package/engine-examples/mines/math/mines-3-20.tsv +2 -0
  327. package/engine-examples/mines/math/mines-3-21.tsv +2 -0
  328. package/engine-examples/mines/math/mines-3-22.tsv +2 -0
  329. package/engine-examples/mines/math/mines-3-3.tsv +2 -0
  330. package/engine-examples/mines/math/mines-3-4.tsv +2 -0
  331. package/engine-examples/mines/math/mines-3-5.tsv +2 -0
  332. package/engine-examples/mines/math/mines-3-6.tsv +2 -0
  333. package/engine-examples/mines/math/mines-4-1.tsv +2 -0
  334. package/engine-examples/mines/math/mines-4-13.tsv +2 -0
  335. package/engine-examples/mines/math/mines-4-14.tsv +2 -0
  336. package/engine-examples/mines/math/mines-4-15.tsv +2 -0
  337. package/engine-examples/mines/math/mines-4-16.tsv +2 -0
  338. package/engine-examples/mines/math/mines-4-17.tsv +2 -0
  339. package/engine-examples/mines/math/mines-4-18.tsv +2 -0
  340. package/engine-examples/mines/math/mines-4-19.tsv +2 -0
  341. package/engine-examples/mines/math/mines-4-2.tsv +2 -0
  342. package/engine-examples/mines/math/mines-4-20.tsv +2 -0
  343. package/engine-examples/mines/math/mines-4-21.tsv +2 -0
  344. package/engine-examples/mines/math/mines-4-3.tsv +2 -0
  345. package/engine-examples/mines/math/mines-4-4.tsv +2 -0
  346. package/engine-examples/mines/math/mines-4-5.tsv +2 -0
  347. package/engine-examples/mines/math/mines-4-6.tsv +2 -0
  348. package/engine-examples/mines/math/mines-4-7.tsv +2 -0
  349. package/engine-examples/mines/math/mines-4-8.tsv +2 -0
  350. package/engine-examples/mines/math/mines-4-9.tsv +2 -0
  351. package/engine-examples/mines/math/mines-5-1.tsv +2 -0
  352. package/engine-examples/mines/math/mines-5-10.tsv +2 -0
  353. package/engine-examples/mines/math/mines-5-11.tsv +2 -0
  354. package/engine-examples/mines/math/mines-5-12.tsv +2 -0
  355. package/engine-examples/mines/math/mines-5-13.tsv +2 -0
  356. package/engine-examples/mines/math/mines-5-14.tsv +2 -0
  357. package/engine-examples/mines/math/mines-5-15.tsv +2 -0
  358. package/engine-examples/mines/math/mines-5-16.tsv +2 -0
  359. package/engine-examples/mines/math/mines-5-17.tsv +2 -0
  360. package/engine-examples/mines/math/mines-5-18.tsv +2 -0
  361. package/engine-examples/mines/math/mines-5-19.tsv +2 -0
  362. package/engine-examples/mines/math/mines-5-2.tsv +2 -0
  363. package/engine-examples/mines/math/mines-5-20.tsv +2 -0
  364. package/engine-examples/mines/math/mines-5-3.tsv +2 -0
  365. package/engine-examples/mines/math/mines-5-4.tsv +2 -0
  366. package/engine-examples/mines/math/mines-5-5.tsv +2 -0
  367. package/engine-examples/mines/math/mines-5-6.tsv +2 -0
  368. package/engine-examples/mines/math/mines-5-7.tsv +2 -0
  369. package/engine-examples/mines/math/mines-5-8.tsv +2 -0
  370. package/engine-examples/mines/math/mines-5-9.tsv +2 -0
  371. package/engine-examples/mines/math/mines-6-1.tsv +2 -0
  372. package/engine-examples/mines/math/mines-6-10.tsv +2 -0
  373. package/engine-examples/mines/math/mines-6-11.tsv +2 -0
  374. package/engine-examples/mines/math/mines-6-12.tsv +2 -0
  375. package/engine-examples/mines/math/mines-6-13.tsv +2 -0
  376. package/engine-examples/mines/math/mines-6-14.tsv +2 -0
  377. package/engine-examples/mines/math/mines-6-15.tsv +2 -0
  378. package/engine-examples/mines/math/mines-6-16.tsv +2 -0
  379. package/engine-examples/mines/math/mines-6-17.tsv +2 -0
  380. package/engine-examples/mines/math/mines-6-18.tsv +2 -0
  381. package/engine-examples/mines/math/mines-6-19.tsv +2 -0
  382. package/engine-examples/mines/math/mines-6-2.tsv +2 -0
  383. package/engine-examples/mines/math/mines-6-3.tsv +2 -0
  384. package/engine-examples/mines/math/mines-6-4.tsv +2 -0
  385. package/engine-examples/mines/math/mines-6-5.tsv +2 -0
  386. package/engine-examples/mines/math/mines-6-6.tsv +2 -0
  387. package/engine-examples/mines/math/mines-6-7.tsv +2 -0
  388. package/engine-examples/mines/math/mines-6-8.tsv +2 -0
  389. package/engine-examples/mines/math/mines-6-9.tsv +2 -0
  390. package/engine-examples/mines/math/mines-7-1.tsv +2 -0
  391. package/engine-examples/mines/math/mines-7-10.tsv +2 -0
  392. package/engine-examples/mines/math/mines-7-11.tsv +2 -0
  393. package/engine-examples/mines/math/mines-7-12.tsv +2 -0
  394. package/engine-examples/mines/math/mines-7-13.tsv +2 -0
  395. package/engine-examples/mines/math/mines-7-14.tsv +2 -0
  396. package/engine-examples/mines/math/mines-7-15.tsv +2 -0
  397. package/engine-examples/mines/math/mines-7-16.tsv +2 -0
  398. package/engine-examples/mines/math/mines-7-17.tsv +2 -0
  399. package/engine-examples/mines/math/mines-7-18.tsv +2 -0
  400. package/engine-examples/mines/math/mines-7-2.tsv +2 -0
  401. package/engine-examples/mines/math/mines-7-3.tsv +2 -0
  402. package/engine-examples/mines/math/mines-7-4.tsv +2 -0
  403. package/engine-examples/mines/math/mines-7-5.tsv +2 -0
  404. package/engine-examples/mines/math/mines-7-6.tsv +2 -0
  405. package/engine-examples/mines/math/mines-7-7.tsv +2 -0
  406. package/engine-examples/mines/math/mines-7-8.tsv +2 -0
  407. package/engine-examples/mines/math/mines-7-9.tsv +2 -0
  408. package/engine-examples/mines/math/mines-8-1.tsv +2 -0
  409. package/engine-examples/mines/math/mines-8-10.tsv +2 -0
  410. package/engine-examples/mines/math/mines-8-11.tsv +2 -0
  411. package/engine-examples/mines/math/mines-8-12.tsv +2 -0
  412. package/engine-examples/mines/math/mines-8-13.tsv +2 -0
  413. package/engine-examples/mines/math/mines-8-14.tsv +2 -0
  414. package/engine-examples/mines/math/mines-8-15.tsv +2 -0
  415. package/engine-examples/mines/math/mines-8-16.tsv +2 -0
  416. package/engine-examples/mines/math/mines-8-17.tsv +2 -0
  417. package/engine-examples/mines/math/mines-8-2.tsv +2 -0
  418. package/engine-examples/mines/math/mines-8-3.tsv +2 -0
  419. package/engine-examples/mines/math/mines-8-4.tsv +2 -0
  420. package/engine-examples/mines/math/mines-8-5.tsv +2 -0
  421. package/engine-examples/mines/math/mines-8-6.tsv +2 -0
  422. package/engine-examples/mines/math/mines-8-7.tsv +2 -0
  423. package/engine-examples/mines/math/mines-8-8.tsv +2 -0
  424. package/engine-examples/mines/math/mines-8-9.tsv +2 -0
  425. package/engine-examples/mines/math/mines-9-1.tsv +2 -0
  426. package/engine-examples/mines/math/mines-9-10.tsv +2 -0
  427. package/engine-examples/mines/math/mines-9-11.tsv +2 -0
  428. package/engine-examples/mines/math/mines-9-12.tsv +2 -0
  429. package/engine-examples/mines/math/mines-9-13.tsv +2 -0
  430. package/engine-examples/mines/math/mines-9-14.tsv +2 -0
  431. package/engine-examples/mines/math/mines-9-15.tsv +2 -0
  432. package/engine-examples/mines/math/mines-9-16.tsv +2 -0
  433. package/engine-examples/mines/math/mines-9-2.tsv +2 -0
  434. package/engine-examples/mines/math/mines-9-3.tsv +2 -0
  435. package/engine-examples/mines/math/mines-9-4.tsv +2 -0
  436. package/engine-examples/mines/math/mines-9-5.tsv +2 -0
  437. package/engine-examples/mines/math/mines-9-6.tsv +2 -0
  438. package/engine-examples/mines/math/mines-9-7.tsv +2 -0
  439. package/engine-examples/mines/math/mines-9-8.tsv +2 -0
  440. package/engine-examples/mines/math/mines-9-9.tsv +2 -0
  441. package/engine-examples/plinko/AGENTS.md +3 -0
  442. package/engine-examples/plinko/README.md +9 -0
  443. package/engine-examples/plinko/bundle/assets.json +1 -0
  444. package/engine-examples/plinko/bundle/game.css +5 -0
  445. package/engine-examples/plinko/bundle/game.js +92 -0
  446. package/engine-examples/plinko/bundle/hbtest.json +4 -0
  447. package/engine-examples/plinko/bundle/index.html +16 -0
  448. package/engine-examples/plinko/bundle/model.json +1 -0
  449. package/engine-examples/plinko/math/gen.mjs +40 -0
  450. package/engine-examples/plinko/math/manifest.json +1 -0
  451. package/engine-examples/plinko/math/plinko-12-high.tsv +13 -0
  452. package/engine-examples/plinko/math/plinko-12-low.tsv +13 -0
  453. package/engine-examples/plinko/math/plinko-12-medium.tsv +13 -0
  454. package/engine-examples/plinko/math/plinko-16-high.tsv +17 -0
  455. package/engine-examples/plinko/math/plinko-16-low.tsv +17 -0
  456. package/engine-examples/plinko/math/plinko-16-medium.tsv +17 -0
  457. package/engine-examples/plinko/math/plinko-8-high.tsv +9 -0
  458. package/engine-examples/plinko/math/plinko-8-low.tsv +9 -0
  459. package/engine-examples/plinko/math/plinko-8-medium.tsv +9 -0
  460. package/engine-examples/plinko/math-kit/pack.mjs +165 -0
  461. package/engine-examples/plinko/math-kit/package.json +8 -0
  462. package/engine-examples/plinko/math-kit/slot.mjs +94 -0
  463. package/engine-examples/plinko/math-kit/tumble.mjs +94 -0
  464. package/engine-examples/slot/AGENTS.md +3 -0
  465. package/engine-examples/slot/README.md +26 -0
  466. package/engine-examples/slot/bundle/assets.json +1 -0
  467. package/engine-examples/slot/bundle/game.css +19 -0
  468. package/engine-examples/slot/bundle/game.js +42 -0
  469. package/engine-examples/slot/bundle/hbtest.json +4 -0
  470. package/engine-examples/slot/bundle/index.html +23 -0
  471. package/engine-examples/slot/math/gen.mjs +79 -0
  472. package/engine-examples/slot/math/manifest.json +1 -0
  473. package/engine-examples/slot/math/slot.tsv +110592 -0
  474. package/engine-examples/tower/AGENTS.md +3 -0
  475. package/engine-examples/tower/README.md +21 -0
  476. package/engine-examples/tower/bundle/assets.json +1 -0
  477. package/engine-examples/tower/bundle/game.css +8 -0
  478. package/engine-examples/tower/bundle/game.js +37 -0
  479. package/engine-examples/tower/bundle/hbtest.json +5 -0
  480. package/engine-examples/tower/bundle/index.html +16 -0
  481. package/engine-examples/tower/math/floor.tsv +4 -0
  482. package/engine-examples/tower/math/manifest.json +61 -0
  483. package/engine-examples/tumble/AGENTS.md +3 -0
  484. package/engine-examples/tumble/README.md +18 -0
  485. package/engine-examples/tumble/bundle/assets.json +1 -0
  486. package/engine-examples/tumble/bundle/game.css +13 -0
  487. package/engine-examples/tumble/bundle/game.js +119 -0
  488. package/engine-examples/tumble/bundle/hbtest.json +4 -0
  489. package/engine-examples/tumble/bundle/i18n.json +122 -0
  490. package/engine-examples/tumble/bundle/index.html +20 -0
  491. package/engine-examples/tumble/bundle/model.json +1 -0
  492. package/engine-examples/tumble/math/gen.mjs +38 -0
  493. package/engine-examples/tumble/math-kit/pack.mjs +165 -0
  494. package/engine-examples/tumble/math-kit/package.json +8 -0
  495. package/engine-examples/tumble/math-kit/slot.mjs +94 -0
  496. package/engine-examples/tumble/math-kit/tumble.mjs +94 -0
  497. package/engine-examples/wheel/README.md +14 -0
  498. package/engine-examples/wheel/bundle/assets.json +1 -0
  499. package/engine-examples/wheel/bundle/game.css +7 -0
  500. package/engine-examples/wheel/bundle/game.js +51 -0
  501. package/engine-examples/wheel/bundle/hbtest.json +4 -0
  502. package/engine-examples/wheel/bundle/index.html +19 -0
  503. package/engine-examples/wheel/math/high.tsv +24 -0
  504. package/engine-examples/wheel/math/low.tsv +24 -0
  505. package/engine-examples/wheel/math/manifest.json +48 -0
  506. package/engine-examples/wheel/math/medium.tsv +24 -0
  507. package/engine-math/pack.mjs +165 -0
  508. package/engine-math/package.json +1 -0
  509. package/engine-math/slot.mjs +94 -0
  510. package/engine-math/tumble.mjs +94 -0
  511. package/engine-mcp/package.json +1 -0
  512. package/engine-mcp/server.mjs +151 -0
  513. package/engine-sdk/GUIDE.md +855 -0
  514. package/engine-sdk/assets.js +92 -0
  515. package/engine-sdk/autoplay.js +82 -0
  516. package/engine-sdk/bet-field.js +391 -0
  517. package/engine-sdk/kit/fiat-coins.js +51 -0
  518. package/engine-sdk/kit/numeric-input.js +116 -0
  519. package/engine-sdk/kit/wallet-format.js +423 -0
  520. package/engine-sdk/marks.js +22 -0
  521. package/engine-sdk/messages.js +289 -0
  522. package/engine-sdk/money.js +89 -0
  523. package/engine-sdk/package.json +1 -0
  524. package/engine-sdk/protocol.js +263 -0
  525. package/engine-sdk/sdk.d.ts +138 -0
  526. package/engine-sdk/sdk.js +330 -0
  527. package/engine-sdk/stake.js +68 -0
  528. package/engine-sdk/ui.d.ts +36 -0
  529. package/engine-sdk/ui.js +340 -0
  530. package/engine-shell/core/faults.js +127 -0
  531. package/engine-shell/core/rgs.js +193 -0
  532. package/engine-shell/core/router.js +917 -0
  533. package/engine-shell/host/boot.js +408 -0
  534. package/engine-shell/host/engine-v1.js +438 -0
  535. package/engine-shell/host/fair.js +285 -0
  536. package/engine-shell/host/fonts/SOURCE.json +11 -0
  537. package/engine-shell/host/fonts/figtree-400.woff2 +0 -0
  538. package/engine-shell/host/fonts/figtree-500.woff2 +0 -0
  539. package/engine-shell/host/fonts/figtree-600.woff2 +0 -0
  540. package/engine-shell/host/fonts/red-hat-display-700.woff2 +0 -0
  541. package/engine-shell/host/index.html +230 -0
  542. package/engine-shell/host/page.js +809 -0
  543. package/engine-shell/host/sandbox-panel.js +681 -0
  544. package/engine-shell/host/sandbox-scenarios.js +554 -0
  545. package/engine-shell/host/site-bridge.js +87 -0
  546. package/engine-shell/host/strings.js +916 -0
  547. package/engine-shell/package.json +1 -0
  548. package/engine-shell/verifiers/badcat/coin.tsv +2 -0
  549. package/engine-shell/verifiers/badcat/dice-2.tsv +11 -0
  550. package/engine-shell/verifiers/badcat/dice-5.tsv +26 -0
  551. package/engine-shell/verifiers/badcat/dice-9.tsv +46 -0
  552. package/engine-shell/verifiers/badcat/manifest.json +3344 -0
  553. package/engine-shell/verifiers/badcat/rps.tsv +3 -0
  554. package/engine-shell/verifiers/badcat/triple.tsv +66 -0
  555. package/engine-shell/verifiers/engine-verify.js +358 -0
  556. package/engine-shell/verifiers/package.json +5 -0
  557. package/engine-shell/verifiers/parity/manifest.json +27 -0
  558. package/engine-shell/verifiers/parity/wide.tsv +5 -0
  559. package/engine-shell/verifiers/parity-vectors.json +244 -0
  560. package/engine-shell/verifiers/vectors.json +100 -0
  561. package/package.json +10 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Staged loading. A game declares its files in groups (assets.json) and
3
+ * loads them when it needs them: `boot` before the first frame, the rest
4
+ * after `ready()` or on demand. The upload checks hold every group to its
5
+ * byte budget, so the first frame never waits for music it does not play.
6
+ *
7
+ * {
8
+ * "groups": {
9
+ * "boot": { "files": ["index.js", "ui.css", "lobby.webp"] },
10
+ * "music": { "files": ["music/theme.mp3"], "load": "idle" },
11
+ * "skins": { "files": ["skins/tabby.webp"], "load": "demand" }
12
+ * }
13
+ * }
14
+ *
15
+ * `load` is when a group is fetched: "boot" (the group named boot, always
16
+ * first), "idle" (after ready, in declaration order) or "demand" (only when
17
+ * the game asks). Files are fetched into the browser cache; the game then
18
+ * uses their ordinary relative URLs.
19
+ */
20
+
21
+ const LOADS = ['boot', 'idle', 'demand'];
22
+ const GROUP_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
23
+ const FILE_RE = /^(?!\/)(?!.*\.\.)[A-Za-z0-9._\/-]{1,200}$/;
24
+
25
+ /** Checks an assets manifest's shape. Returns the problems, empty when fine. */
26
+ export function checkAssetManifest(manifest) {
27
+ const problems = [];
28
+ const groups = manifest && manifest.groups;
29
+ if (!groups || typeof groups !== 'object' || Array.isArray(groups)) return ['groups must be an object'];
30
+ if (!groups.boot) problems.push('a boot group is required');
31
+ const seen = new Map();
32
+ for (const [name, g] of Object.entries(groups)) {
33
+ if (!GROUP_RE.test(name)) problems.push(`group name ${JSON.stringify(name)}`);
34
+ if (!g || !Array.isArray(g.files) || g.files.length === 0) {
35
+ problems.push(`group ${name} needs files`);
36
+ continue;
37
+ }
38
+ const load = name === 'boot' ? 'boot' : g.load ?? 'idle';
39
+ if (!LOADS.includes(load) || (load === 'boot') !== (name === 'boot')) problems.push(`group ${name} load ${JSON.stringify(g.load)}`);
40
+ for (const f of g.files) {
41
+ if (typeof f !== 'string' || !FILE_RE.test(f)) problems.push(`group ${name} file ${JSON.stringify(f)}`);
42
+ else if (seen.has(f)) problems.push(`${f} is in both ${seen.get(f)} and ${name}`);
43
+ else seen.set(f, name);
44
+ }
45
+ }
46
+ return problems;
47
+ }
48
+
49
+ /**
50
+ * Creates the loader. `onProgress(group, loadedFiles, totalFiles)` reports
51
+ * each group (the SDK forwards it to the shell as `loading`).
52
+ */
53
+ export function createAssetLoader(manifest, { base = './', fetchFile = (url) => fetch(url), onProgress = () => {}, concurrency = 6 } = {}) {
54
+ const problems = checkAssetManifest(manifest);
55
+ if (problems.length) throw new TypeError(`assets.json: ${problems.join('; ')}`);
56
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1) throw new RangeError('asset concurrency must be a positive integer');
57
+ const groups = manifest.groups;
58
+ const loading = new Map(); // group -> Promise
59
+
60
+ function load(name) {
61
+ if (!Object.hasOwn(groups, name)) return Promise.reject(new TypeError(`unknown asset group ${name}`));
62
+ if (!loading.has(name)) loading.set(name, fetchGroup(name));
63
+ return loading.get(name);
64
+ }
65
+
66
+ async function fetchGroup(name) {
67
+ const files = groups[name].files;
68
+ let done = 0;
69
+ let next = 0;
70
+ onProgress(name, 0, files.length);
71
+ async function worker() {
72
+ while (next < files.length) {
73
+ const file = files[next++];
74
+ // checkAssetManifest admitted only plain relative paths.
75
+ const res = await fetchFile(base + file);
76
+ if (!res || !res.ok) throw new Error(`asset ${file}: ${res ? res.status : 'no response'}`);
77
+ await res.arrayBuffer(); // into the cache, whatever the type
78
+ onProgress(name, ++done, files.length);
79
+ }
80
+ }
81
+ await Promise.all(Array.from({ length: Math.min(concurrency, files.length) }, worker));
82
+ }
83
+
84
+ /** Loads the idle groups one after another, in declaration order. */
85
+ async function loadIdle() {
86
+ for (const [name, g] of Object.entries(groups)) {
87
+ if (name !== 'boot' && (g.load ?? 'idle') === 'idle') await load(name);
88
+ }
89
+ }
90
+
91
+ return { load, loadBoot: () => load('boot'), loadIdle };
92
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * The Originals' autobet rules, as the engine panel (ui.js) turns them into
3
+ * a shell autoplay session. Pure, so the rules are tested without a DOM.
4
+ *
5
+ * The session is the shell's (protocol.js AUTOPLAY_START): it holds at most
6
+ * `rounds` plays of the bet it was asked for, none above `stake`, and ends at
7
+ * the player's stop on profit or loss (the shell shows the player all of it
8
+ * before Start). The panel keeps the Originals' settings — a count (0 = as many as
9
+ * the shell allows), on win / on loss reset the stake or increase it by a
10
+ * percent, stop on profit / stop on loss — and asks for the session those
11
+ * settings can need: the stake it starts with when both rules reset, or the
12
+ * most the player could stake (the coin's maximum, the selected bet's own
13
+ * ceiling under the maximum win and the balance) when a rule grows it. A
14
+ * grown stake is cut to that ceiling, never above it: the shell refuses a
15
+ * play above the session's stake. The request names the bet (`mode`, `bet`),
16
+ * so the shell caps the stake at the bet's own ceiling
17
+ * (`limits.maxStake[mode][bet]`) as well as the coin's maximum and the
18
+ * balance; the panel applies the same ceiling (field.maxStake) so the terms
19
+ * it asks for are the ones the player will see.
20
+ */
21
+ import { AUTOPLAY_MAX_ROUNDS } from './protocol.js';
22
+
23
+ // Truncated down to dp decimals, with the float noise scrubbed first (as in
24
+ // bet-field.js): a stake never grows past what the rule gives.
25
+ export function trunc(n, dp) {
26
+ if (!Number.isFinite(n) || n <= 0) return 0;
27
+ const f = 10 ** dp;
28
+ return Math.floor(parseFloat((n * f).toPrecision(15))) / f;
29
+ }
30
+
31
+ /**
32
+ * The most a stake may reach: the coin's max, the bet's own ceiling
33
+ * (bet-field.js `maxStake`, from protocol.js maxStakeFor) and the balance,
34
+ * whichever is lowest; `amount` when none is known. A bet ceiling of 0 (the
35
+ * bet takes nothing while the coin has no rate) never gets here: the field
36
+ * refuses the stake before a run starts.
37
+ */
38
+ export function stakeCeiling({ amount, limits = {}, balance = null, maxStake = null }) {
39
+ const bounds = [];
40
+ if (typeof limits.max === 'number' && limits.max > 0) bounds.push(limits.max);
41
+ if (typeof maxStake === 'number' && maxStake > 0) bounds.push(maxStake);
42
+ if (typeof balance === 'number' && balance >= 0) bounds.push(balance);
43
+ if (!bounds.length) return amount;
44
+ return Math.max(amount, Math.min(...bounds));
45
+ }
46
+
47
+ /**
48
+ * The session the panel asks the shell for.
49
+ * mode, bet the bet the run is for (the panel's selection; the shell holds every play to it)
50
+ * amount the stake the run starts with (wallet units)
51
+ * count the player's number of bets; 0 is "infinite", which is the shell's cap
52
+ * rule { win: 'reset'|'increase', loss: 'reset'|'increase' }
53
+ * winPct, lossPct the percent a rule increases by
54
+ * stopProfit, stopLoss the player's stops (0 = none)
55
+ * limits, balance, maxStake what bounds a grown stake: the coin's limits, the
56
+ * balance, and the selected bet's own ceiling (null when the session names none)
57
+ */
58
+ export function autoplayRequest({ mode, bet, amount, count = 0, rule = { win: 'reset', loss: 'reset' }, winPct = 0, lossPct = 0, stopProfit = 0, stopLoss = 0, limits = {}, balance = null, maxStake = null }) {
59
+ const rounds = count > 0 ? Math.min(Math.floor(count), AUTOPLAY_MAX_ROUNDS) : AUTOPLAY_MAX_ROUNDS;
60
+ const grows = (rule.win === 'increase' && winPct > 0) || (rule.loss === 'increase' && lossPct > 0);
61
+ const req = { rounds, stake: grows ? stakeCeiling({ amount, limits, balance, maxStake }) : amount };
62
+ if (typeof mode === 'string') req.mode = mode;
63
+ if (typeof bet === 'string') req.bet = bet;
64
+ if (stopProfit > 0) req.stopOnProfit = stopProfit;
65
+ if (stopLoss > 0) req.stopOnLoss = stopLoss;
66
+ return req;
67
+ }
68
+
69
+ /** The stake of the next round after a win or a loss, within the session's stake. */
70
+ export function nextStake({ amount, base, won, rule, winPct = 0, lossPct = 0, cap, decimals = 8 }) {
71
+ const how = won ? rule.win : rule.loss;
72
+ const pct = Math.max(0, won ? winPct : lossPct);
73
+ const next = how === 'increase' ? amount * (1 + pct / 100) : base;
74
+ return trunc(Math.min(next, cap), decimals);
75
+ }
76
+
77
+ /** Why the run stops on its result so far: 'profit', 'loss', or null to go on. */
78
+ export function autoplayStop({ profit, stopProfit = 0, stopLoss = 0 }) {
79
+ if (stopProfit > 0 && profit >= stopProfit) return 'profit';
80
+ if (stopLoss > 0 && -profit >= stopLoss) return 'loss';
81
+ return null;
82
+ }
@@ -0,0 +1,391 @@
1
+ /**
2
+ * The Originals' money fields, for any game: drop them in, connect them to
3
+ * the engine, and the player sees their own currency exactly as in Dice or
4
+ * Coin Flip — our coin mark, the fiat sign, the field's precision, the
5
+ * caption in the other unit, ½ · 2× · max — with nothing to build.
6
+ *
7
+ * import './engine-sdk/bet-field.js';
8
+ *
9
+ * <hb-bet-amount></hb-bet-amount> the stake (label="…", max-button)
10
+ * <hb-balance></hb-balance> the balance, as the header shows it
11
+ *
12
+ * field.connect(engine);
13
+ * field.bet = { mode, bet }; // the bet the stake is for (its own ceiling)
14
+ * engine.play({ mode, bet, amount: field.amount }); // wallet units
15
+ * field.disabled = true; // while a round runs
16
+ * field.addEventListener('change', (e) => e.detail); // { amount, reason }
17
+ * field.value; // the same as text, like an <input>
18
+ *
19
+ * The rule is the Originals' (bet field: coinflip-frontend BetAmount.svelte,
20
+ * money: game-kit wallet-format.js through engine.money): a typed amount is in
21
+ * the field's unit and becomes the wallet amount truncated down, never more
22
+ * than typed; an empty field is no stake (`reason` is `min`: an engine round
23
+ * is never played for free, so a play button stays disabled on it); a stake
24
+ * the server would refuse (over the balance or the limits) turns the field
25
+ * red and says why, and the game should not send it. The limits include the
26
+ * maximum win per round: each bet has its own highest stake
27
+ * (engine.config.limits.maxStake[mode][bet], lower the higher the bet's top
28
+ * multiplier), so tell the field which bet the stake is for and it holds the
29
+ * stake to it, says the most the bet takes, and its max chip stops there. A
30
+ * figure of 0 (the server names it while the coin has no USD rate) means
31
+ * nothing can be staked for now: the field says so in the Originals' words
32
+ * and hides its max chip, and a play button on `reason` stays disabled. The
33
+ * server checks everything again anyway.
34
+ *
35
+ * Shadow DOM: the game's styles cannot reach in, and these cannot leak out.
36
+ * The look follows the site's tokens; a game may set --hb-font.
37
+ */
38
+
39
+ import { MESSAGES } from './messages.js';
40
+ import { maxStakeFor } from './protocol.js';
41
+ import { leastStake, stakeCeiling, stakeReason } from './stake.js';
42
+
43
+ const CSS = `
44
+ :host { display: block; font-family: var(--hb-font, 'Figtree', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); color: #F9F9FB; }
45
+ .row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; min-width: 0; margin-bottom: 8px; }
46
+ .label { font-size: 14px; line-height: 20px; font-weight: 600; }
47
+ .cap { font-size: 14px; line-height: 20px; font-weight: 600; color: #8F96A8; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
48
+ .hint { font-size: 12px; line-height: 16px; color: #8F96A8; min-height: 16px; margin-top: 6px; }
49
+ .hint.bad { color: #FA2E5D; }
50
+ .field { position: relative; box-sizing: border-box; display: flex; align-items: center; gap: 8px; height: 48px; width: 100%; padding: 0 4px 0 16px;
51
+ background: #0b1f28; border: 1px solid #0D2A38; border-radius: 8px; transition: border-color 120ms ease, box-shadow 120ms ease; }
52
+ .field:focus-within { box-shadow: 0 0 0 4px #08141a, 0 0 0 6px #28F3F3; }
53
+ .field.bad { border-color: #FA2E5D; box-shadow: 0 0 0 1px #FA2E5D inset; }
54
+ .field.shake { animation: shake 200ms ease; }
55
+ @keyframes shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }
56
+ .mark { flex: 0 0 auto; width: 16px; height: 16px; display: block; border-radius: 50%; }
57
+ .sign { flex: 0 0 auto; white-space: nowrap; font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; margin-right: -8px; }
58
+ .sign.empty { color: #8F96A8; }
59
+ input { flex: 1 1 0; min-width: 0; height: 100%; padding: 0; background: transparent; border: 0; outline: none; color: #F9F9FB;
60
+ font: inherit; font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; }
61
+ input::placeholder { color: #8F96A8; }
62
+ input[readonly] { color: #8F96A8; cursor: not-allowed; }
63
+ .chips { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
64
+ button { display: inline-flex; align-items: center; justify-content: center; min-width: 34px; height: 32px; padding: 0 8px; border: 0; border-radius: 8px;
65
+ background: rgba(9, 102, 125, 0.15); color: #F9F9FB; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; user-select: none;
66
+ transition: background 120ms ease, transform 120ms ease; }
67
+ button:hover, button:focus-visible { background: rgba(9, 102, 125, 0.2); outline: none; }
68
+ button:active { transform: scale(0.96); }
69
+ button:disabled { opacity: 0.45; cursor: not-allowed; transform: none; }
70
+ .balance { display: inline-flex; align-items: center; gap: 6px; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
71
+ `;
72
+
73
+ // The field's words are the Originals' own, in every language the site
74
+ // ships (messages.js, generated from Coin Flip's dictionaries). The language
75
+ // is the player's: the field's `lang`, the shell's, the page's, the browser's.
76
+ export function wordsFor(lang) {
77
+ const v = String(lang || '').toLowerCase().replace('_', '-');
78
+ return MESSAGES[v] || MESSAGES[v.split('-')[0]] || MESSAGES.en;
79
+ }
80
+
81
+ // Truncated down to dp decimals, with the float noise scrubbed first (as in
82
+ // wallet-format.js): a stake never grows past what the player chose.
83
+ function trunc(n, dp) {
84
+ if (!Number.isFinite(n) || n <= 0) return 0;
85
+ const f = 10 ** dp;
86
+ return Math.floor(parseFloat((n * f).toPrecision(15))) / f;
87
+ }
88
+
89
+ function sheet(root) {
90
+ const style = document.createElement('style');
91
+ style.textContent = CSS;
92
+ root.append(style);
93
+ }
94
+
95
+ export class BetAmountField extends HTMLElement {
96
+ static observedAttributes = ['label', 'disabled', 'max-button', 'lang'];
97
+ #engine = null;
98
+ #money = null;
99
+ #amount = 0;
100
+ #bet = null; // { mode, bet } the stake is for: its own ceiling under the maximum win
101
+ #typing = false;
102
+ #hintTimer = 0;
103
+ #note = '';
104
+ #els;
105
+
106
+ constructor() {
107
+ super();
108
+ const root = this.attachShadow({ mode: 'open' });
109
+ sheet(root);
110
+ const wrap = document.createElement('div');
111
+ wrap.innerHTML = `<div class="row"><span class="label" part="label"></span><span class="cap" part="caption"></span></div>
112
+ <div class="field" part="field"><img class="mark" alt="" hidden><span class="sign" hidden></span>
113
+ <input inputmode="decimal" autocomplete="off" spellcheck="false" readonly>
114
+ <span class="chips"><button type="button" data-chip="half">½</button><button type="button" data-chip="double">2×</button><button type="button" data-chip="max" hidden>max</button></span></div>
115
+ <div class="hint" part="hint" aria-live="polite"></div>`;
116
+ root.append(wrap);
117
+ const $ = (s) => root.querySelector(s);
118
+ this.#els = { label: $('.label'), cap: $('.cap'), field: $('.field'), mark: $('.mark'), sign: $('.sign'), input: $('input'), hint: $('.hint'), chips: [...root.querySelectorAll('button')] };
119
+ const { input } = this.#els;
120
+ input.addEventListener('focus', () => input.select());
121
+ input.addEventListener('input', () => this.#onInput());
122
+ input.addEventListener('blur', () => this.#onBlur());
123
+ for (const b of this.#els.chips) b.addEventListener('click', () => this.#chip(b.dataset.chip));
124
+ this.#render();
125
+ }
126
+
127
+ /** Binds the field to the engine: its currency, limits and balance. */
128
+ connect(engine) {
129
+ this.#engine = engine;
130
+ this.#money = engine.money;
131
+ engine.on('balance', () => this.#render());
132
+ const kept = this.#recall();
133
+ if (kept > 0) this.#amount = kept;
134
+ this.#render();
135
+ return this;
136
+ }
137
+
138
+ /** The stake in wallet units, what engine.play takes. */
139
+ get amount() {
140
+ return this.#amount;
141
+ }
142
+
143
+ set amount(v) {
144
+ this.#set(Number(v) || 0);
145
+ }
146
+
147
+ /** The stake as text, like an <input>'s value: for code written against
148
+ * one (a plain decimal in wallet units, never an exponent). */
149
+ get value() {
150
+ if (!this.#money) return '0';
151
+ const text = this.#amount.toFixed(this.#money.walletDecimals);
152
+ return text.includes('.') ? text.replace(/0+$/, '').replace(/\.$/, '') : text;
153
+ }
154
+
155
+ set value(v) {
156
+ this.#set(this.#money ? this.#money.parse(String(v)) : 0);
157
+ }
158
+
159
+ /** The bet the stake is for ({ mode, bet }), or null: the field holds the
160
+ * stake to that bet's own highest stake under the maximum win per round
161
+ * (engine.config.limits.maxStake[mode][bet]) once it knows the bet. */
162
+ get bet() {
163
+ return this.#bet;
164
+ }
165
+
166
+ set bet(v) {
167
+ const next = v && typeof v.mode === 'string' && typeof v.bet === 'string' ? { mode: v.mode, bet: v.bet } : null;
168
+ if ((next && this.#bet && next.mode === this.#bet.mode && next.bet === this.#bet.bet) || (!next && !this.#bet)) return;
169
+ this.#bet = next;
170
+ this.#render();
171
+ // Another bet may take less: whoever follows `reason` hears it changed.
172
+ this.dispatchEvent(new CustomEvent('change', { detail: { amount: this.#amount, reason: this.reason } }));
173
+ }
174
+
175
+ /** The most the selected bet takes, in wallet units, or null when the
176
+ * session names none for it (protocol.js maxStakeFor). 0 when it names
177
+ * nothing can be staked for now (the coin has no rate). */
178
+ get maxStake() {
179
+ if (!this.#engine || !this.#bet) return null;
180
+ return maxStakeFor(this.#engine.config.limits, this.#bet.mode, this.#bet.bet);
181
+ }
182
+
183
+ /** Why the stake cannot be played now ({ code, message }), or null. An
184
+ * empty field is `min`: there is no free round (stake.js); `maxWin` is
185
+ * over the most the selected bet takes under the maximum win per round,
186
+ * and the message names that figure. When that figure is 0 nothing can
187
+ * be staked until the coin's rate is back, and the message is the
188
+ * Originals' "something went wrong": the server would answer every play
189
+ * with `unavailable`. */
190
+ get reason() {
191
+ const m = this.#money;
192
+ if (!m || !this.#engine) return null;
193
+ const limits = this.#engine.config.limits || {};
194
+ const most = this.maxStake;
195
+ const code = stakeReason(this.#amount, limits, this.#engine.balance, most);
196
+ if (code === 'balance') return { code, message: this.#words.blockOverBalance };
197
+ if (code === 'min') return { code, message: this.#say('blockUnderMin', leastStake(limits, m.walletDecimals)) };
198
+ if (code === 'max') return { code, message: this.#say('blockOverMax', limits.max) };
199
+ if (code === 'maxWin') return { code, message: most === 0 ? this.#words.apiInternalError : this.#say('blockOverMax', most) };
200
+ return null;
201
+ }
202
+
203
+ get disabled() {
204
+ return this.hasAttribute('disabled');
205
+ }
206
+
207
+ set disabled(on) {
208
+ this.toggleAttribute('disabled', !!on);
209
+ }
210
+
211
+ attributeChangedCallback() {
212
+ this.#render();
213
+ }
214
+
215
+ get #words() {
216
+ return wordsFor(this.getAttribute('lang') || (this.#engine && this.#engine.config.locale) || document.documentElement.lang || navigator.language);
217
+ }
218
+
219
+ #say(key, amount) {
220
+ const m = this.#money;
221
+ return this.#words[key].replace('{amount}', m.format(amount)).replace('{currency}', m.unit);
222
+ }
223
+
224
+ #onInput() {
225
+ const { input } = this.#els;
226
+ this.#typing = true;
227
+ let text = this.#money ? this.#money.filter(input) : input.value;
228
+ // No more decimals than the field shows: a stake of 1.239 shown as
229
+ // "1.24" would be a stake the player cannot see.
230
+ const dot = text.indexOf('.');
231
+ if (this.#money && dot >= 0 && text.length - dot - 1 > this.#money.decimals) {
232
+ text = text.slice(0, dot + (this.#money.decimals ? this.#money.decimals + 1 : 0));
233
+ input.value = text;
234
+ }
235
+ if (!this.#money || text.replace(/[\s,]/g, '') === '') return this.#render();
236
+ this.#set(this.#money.parse(text), { typing: true });
237
+ }
238
+
239
+ // Left empty: no stake (the field says the minimum; the panel will not
240
+ // play it). Below the minimum: raised to it, and said so.
241
+ #onBlur() {
242
+ if (!this.#typing) return;
243
+ this.#typing = false;
244
+ if (this.#els.input.value.replace(/[\s,]/g, '') === '') return this.#set(0);
245
+ const min = this.#engine && this.#engine.config.limits && this.#engine.config.limits.min;
246
+ if (typeof min === 'number' && this.#amount > 0 && this.#amount < min) {
247
+ this.#set(min);
248
+ return this.#hint(this.#say('blockUnderMin', min));
249
+ }
250
+ this.#render();
251
+ }
252
+
253
+ #chip(kind) {
254
+ const m = this.#money;
255
+ if (!m || this.disabled) return;
256
+ // The most the field offers: the coin's maximum, the selected bet's own
257
+ // ceiling under the maximum win, the balance — whichever is least.
258
+ const cap = stakeCeiling(this.#engine && this.#engine.config.limits, this.#engine.balance, this.maxStake);
259
+ if (kind === 'half') return this.#set(trunc(this.#amount / 2, m.walletDecimals));
260
+ const want = kind === 'double' ? this.#amount * 2 : cap;
261
+ if (Number.isFinite(want)) this.#set(trunc(Math.min(want, cap), m.walletDecimals));
262
+ }
263
+
264
+ #set(amount, { typing = false } = {}) {
265
+ const m = this.#money;
266
+ const next = m ? trunc(amount, m.walletDecimals) : 0;
267
+ const before = this.reason;
268
+ this.#amount = next;
269
+ if (!typing) this.#typing = false;
270
+ this.#remember();
271
+ this.#render();
272
+ const reason = this.reason;
273
+ // A wrong stake shakes; an empty field is only not yet a stake.
274
+ if (reason && next > 0 && (!before || before.code !== reason.code)) this.#shake();
275
+ // Typing already sends 'input' out of the shadow root (it is composed);
276
+ // a chip or a script sets the amount without one.
277
+ if (!typing) this.dispatchEvent(new Event('input', { bubbles: true }));
278
+ this.dispatchEvent(new CustomEvent('change', { detail: { amount: next, reason } }));
279
+ }
280
+
281
+ /** A note under the field for a few seconds (a refusal's message). */
282
+ note(text) {
283
+ this.#hint(String(text));
284
+ }
285
+
286
+ // A note for a few seconds: money never changes without a word.
287
+ #hint(text) {
288
+ this.#note = text;
289
+ clearTimeout(this.#hintTimer);
290
+ this.#hintTimer = setTimeout(() => {
291
+ this.#note = '';
292
+ this.#render();
293
+ }, 4000);
294
+ this.#render();
295
+ }
296
+
297
+ #shake() {
298
+ const f = this.#els.field;
299
+ f.classList.remove('shake');
300
+ void f.offsetWidth;
301
+ f.classList.add('shake');
302
+ }
303
+
304
+ #render() {
305
+ const e = this.#els;
306
+ if (!e) return;
307
+ const m = this.#money;
308
+ e.label.textContent = this.getAttribute('label') || this.#words.betAmount;
309
+ // No max chip for a bet that takes nothing right now (maxStake 0): there is no most to set.
310
+ e.chips[2].hidden = !this.hasAttribute('max-button') || this.maxStake === 0;
311
+ const off = this.disabled || !m;
312
+ e.input.readOnly = off;
313
+ for (const b of e.chips) b.disabled = off;
314
+ if (!m) return;
315
+ e.mark.src = m.mark;
316
+ e.mark.hidden = false;
317
+ e.sign.textContent = m.sign;
318
+ e.sign.hidden = !m.sign;
319
+ e.input.placeholder = m.format(0);
320
+ if (!this.#typing) e.input.value = this.#amount > 0 ? m.format(this.#amount) : '';
321
+ e.input.setAttribute('aria-label', `${e.label.textContent}, ${m.unit}`);
322
+ e.sign.classList.toggle('empty', e.input.value === '');
323
+ e.cap.textContent = m.caption(this.#amount) || '';
324
+ const reason = this.reason;
325
+ // Red is for a stake the server would refuse; an empty field just
326
+ // says what the least stake is.
327
+ const wrong = !!reason && this.#amount > 0;
328
+ e.field.classList.toggle('bad', wrong);
329
+ e.hint.classList.toggle('bad', wrong);
330
+ e.hint.textContent = reason ? reason.message : this.#note;
331
+ }
332
+
333
+ // The last stake, per game and coin, on this device: the field comes back
334
+ // with what the player last played, as the Originals' does.
335
+ #key() {
336
+ const c = this.#engine && this.#engine.config;
337
+ return c ? `hb-bet:${c.game}:${this.#money.coin}` : null;
338
+ }
339
+
340
+ #remember() {
341
+ const k = this.#key();
342
+ try { if (k) localStorage.setItem(k, String(this.#amount)); } catch { /* storage is a convenience */ }
343
+ }
344
+
345
+ #recall() {
346
+ const k = this.#key();
347
+ let v = 0;
348
+ try { v = k ? Number(localStorage.getItem(k)) : 0; } catch { v = 0; }
349
+ return Number.isFinite(v) && v > 0 ? trunc(v, this.#money.walletDecimals) : 0;
350
+ }
351
+ }
352
+
353
+ export class BalanceField extends HTMLElement {
354
+ #engine = null;
355
+ #el;
356
+
357
+ constructor() {
358
+ super();
359
+ const root = this.attachShadow({ mode: 'open' });
360
+ sheet(root);
361
+ this.#el = document.createElement('span');
362
+ this.#el.className = 'balance';
363
+ this.#el.setAttribute('part', 'balance');
364
+ root.append(this.#el);
365
+ }
366
+
367
+ connect(engine) {
368
+ this.#engine = engine;
369
+ engine.on('balance', () => this.#render());
370
+ this.#render();
371
+ return this;
372
+ }
373
+
374
+ #render() {
375
+ const e = this.#engine;
376
+ if (!e) return;
377
+ const m = e.money;
378
+ const img = document.createElement('img');
379
+ img.className = 'mark';
380
+ img.alt = '';
381
+ img.src = m.mark;
382
+ const text = e.balance === null ? '—' : (m.sign || '') + m.format(e.balance);
383
+ this.#el.replaceChildren(img, document.createTextNode(text + (m.sign ? '' : ' ' + m.unit)));
384
+ this.title = m.caption(e.balance ?? 0) || '';
385
+ }
386
+ }
387
+
388
+ if (globalThis.customElements && !customElements.get('hb-bet-amount')) {
389
+ customElements.define('hb-bet-amount', BetAmountField);
390
+ customElements.define('hb-balance', BalanceField);
391
+ }