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,138 @@
1
+ // Types for @housegames/engine-sdk (sdk.js, ui.js). Editors read them; nothing is built.
2
+
3
+ export type ErrorCode = 'invalid' | 'busy' | 'insufficient-balance' | 'limits' | 'pacing' | 'pending' | 'unavailable' | 'session-ended' | 'round' | 'conflict' | 'server' | 'needs-click' | 'autoplay';
4
+ export const ERRORS: Readonly<Record<'INVALID' | 'BUSY' | 'INSUFFICIENT' | 'LIMITS' | 'PACING' | 'PENDING' | 'UNAVAILABLE' | 'SESSION' | 'ROUND' | 'CONFLICT' | 'SERVER' | 'CLICK' | 'AUTOPLAY', ErrorCode>>;
5
+
6
+ /** A refusal from the shell or the server. */
7
+ export class EngineError extends Error {
8
+ code: ErrorCode;
9
+ /** For pacing and pending: when to try again. */
10
+ retryAfterMs?: number;
11
+ /** For 'round': the round as it stands; redraw it. */
12
+ round?: Round;
13
+ /** For a 'limits' refusal over the maximum win per round: the most the bet takes, a decimal
14
+ * string in the wallet coin as the server wrote it; set the field to it. */
15
+ maxStake?: string;
16
+ }
17
+
18
+ export interface Bet { id: string; label?: string; maxMultBp: number }
19
+ export interface Mode {
20
+ id: string; label?: string; kind: 'table' | 'ladder'; bets: Bet[];
21
+ steps?: number; ladderBp?: number[]; cashout?: boolean; autoAdvance?: boolean;
22
+ }
23
+
24
+ export interface Config {
25
+ game: string;
26
+ mathHash: string;
27
+ env: 'sandbox' | 'production';
28
+ balance?: number;
29
+ currency: { code: string; symbol: string; decimals: number };
30
+ /** In the wallet coin. `maxWin` is the most one round pays (the platform's maximum win per round,
31
+ * or the coin's own, whichever is less); `maxStake[mode][bet]` is the most that bet takes under it
32
+ * (a decimal string at the coin's precision, rounded down: stake × the bet's top multiplier stays
33
+ * within `maxWin`), so a high-multiplier bet has a low one. `"0"` for every bet (and `maxWin` 0)
34
+ * while the coin has no USD rate: nothing can be staked until it is back. Absent on an older server. */
35
+ limits: { min: number; max: number; maxWin: number; maxStake?: Record<string, Record<string, string>> };
36
+ modes: Mode[];
37
+ activeRound?: Round;
38
+ playerTag: string;
39
+ state: { revision: number; data: unknown };
40
+ locale: string;
41
+ /** The jurisdiction's word on autoplay. */
42
+ rg?: { autoplay?: { allowed: boolean } };
43
+ }
44
+
45
+ /** The round as the game replays it. Show it as it comes back: it is the truth. */
46
+ export interface Round {
47
+ id: string; game: string; mathHash: string; mode: string; bet: string; kind: 'table' | 'ladder';
48
+ nonce: number; currency: string; amount: string; step: number; state: 'open' | 'closed' | string; createdAt: string;
49
+ draws: { index: number; raw: string; rejected: number }[];
50
+ /** The row data of each shown draw (what your math/*.tsv holds). */
51
+ data: any[];
52
+ payout?: string; multBp?: number; payoutMultiplier?: number; settledAt?: string;
53
+ survived?: number; busted?: boolean;
54
+ }
55
+
56
+ /** Money exactly as the House Originals show it, in the player's coin or fiat. Amounts are wallet units. */
57
+ export interface Money {
58
+ coin: string; isFiat: boolean; unit: string; sign: string; mark: string;
59
+ decimals: number; walletDecimals: number;
60
+ /** The smallest wallet unit (10⁻⁸ for a coin): the precision a stake is cut to. Never a bet button's increment (nor is limits.min): bet up / down walks levels(). */
61
+ step: number;
62
+ toView(amount: number): number; toWallet(value: number): number;
63
+ format(amount: number): string; caption(amount: number): string; parse(text: string): number;
64
+ payout(amount: number, mult: number): number;
65
+ /** Round stakes in the display unit between min and max (wallet units, engine.config.limits): offer these, never a converted limit. */
66
+ levels(min?: number, max?: number): BetLevel[];
67
+ }
68
+
69
+ /** A bet level: the round number the player sees, the wallet amount engine.play takes, the header's text and the button's label (€2.00, 2 USDT). */
70
+ export interface BetLevel { view: number; amount: number; text: string; label: string; }
71
+
72
+ export type ShellEvent = 'balance' | 'control' | 'pause' | 'resume' | 'ended' | 'autoplay';
73
+ /** The shell's autoplay session: sent when it starts, after each of its plays and when it ends. */
74
+ export interface AutoplayState {
75
+ active: boolean;
76
+ /** Plays left in the session. */
77
+ left: number;
78
+ /** Plays made in it. */
79
+ played: number;
80
+ /** The most a play in it may stake (while active). */
81
+ stake?: number;
82
+ /** Why it ended: stopped, done, profit, loss, refused, paused, ended. */
83
+ reason?: string;
84
+ }
85
+ export interface EventBody {
86
+ balance: { balance: number };
87
+ control: { action: 'sound' | 'settings' };
88
+ pause: {}; resume: {}; ended: {};
89
+ autoplay: AutoplayState;
90
+ }
91
+
92
+ /** What a game asks the shell for on the player's click: at most `rounds` plays (≤ 1000) of one
93
+ * bet (`mode`, `bet`), none above `stake`. The shell shows the player these terms before any play. */
94
+ export interface AutoplayRequest {
95
+ mode: string;
96
+ bet: string;
97
+ rounds: number;
98
+ stake: number;
99
+ /** End the session once its net result reaches this profit / this loss (wallet units). */
100
+ stopOnProfit?: number;
101
+ stopOnLoss?: number;
102
+ }
103
+
104
+ export interface Engine {
105
+ readonly config: Config;
106
+ readonly money: Money;
107
+ /** The balance as the site's header shows it; null while unknown. */
108
+ readonly balance: number | null;
109
+ /** The player's saved data; neither call needs a click (a save moves no money). */
110
+ readonly state: { get(): Promise<{ revision: number; data: unknown }>; put(revision: number, data: unknown): Promise<{ revision: number; data: unknown }> };
111
+ /** Places a bet; rejects with EngineError. Call it from the player's click (or inside an
112
+ * autoplay session): the shell refuses a play with no activation behind it (`needs-click`). */
113
+ play(bet: { mode: string; bet: string; amount: number }): Promise<{ round: Round; balance?: number }>;
114
+ /** Moves an open ladder round. `advance` and `cashout` both need the player's click as a play does
115
+ * (nothing moves without the player; a round left open waits for them, never for the game), or the session's own round. */
116
+ action(round: Round, action: 'advance' | 'cashout'): Promise<{ round: Round; balance?: number }>;
117
+ /** Autoplay is the shell's: ask for a session from the player's click (`needs-click` otherwise) —
118
+ * the shell shows the player the capped terms and resolves this on their Start (rejects with
119
+ * `autoplay` on Cancel, for 10 s after a Cancel, or when their rules forbid it) — play inside it
120
+ * (no click needed, on the bet granted), follow the `autoplay` event; the shell shows rounds left
121
+ * and Stop over the game. */
122
+ readonly autoplay: {
123
+ start(request: AutoplayRequest): Promise<{ session: AutoplayRequest }>;
124
+ stop(): Promise<{ stopped: true }>;
125
+ };
126
+ /** The settled round as the game may see it, without its seeds; the shell's dialog (openFairness) holds the receipt. */
127
+ round(roundId: string): Promise<unknown>;
128
+ on<T extends ShellEvent>(type: T, fn: (body: EventBody[T]) => void): () => void;
129
+ /** The first frame is on screen. */
130
+ ready(): void;
131
+ loading(group: string, loaded: number, total: number): void;
132
+ /** The round's outcome is on screen: the header balance follows now. */
133
+ revealed(roundId: string): void;
134
+ openFairness(roundId: string): void;
135
+ reportError(message: string): void;
136
+ }
137
+
138
+ export function connect(options?: { timeoutMs?: number }): Promise<Engine>;
@@ -0,0 +1,330 @@
1
+ /**
2
+ * @housegames/engine-sdk — what a studio game imports.
3
+ *
4
+ * import { connect } from '@housegames/engine-sdk';
5
+ *
6
+ * const engine = await connect();
7
+ * engine.config; // game, modes, currency, limits, balance, state…
8
+ * engine.money; // the player's currency, as the Originals show it
9
+ * const field = document.querySelector('hb-bet-amount'); // bet-field.js
10
+ * field.connect(engine);
11
+ * const { round, balance } = await engine.play({ mode: 'coin', bet: 'heads', amount: field.amount });
12
+ * await engine.action(round, 'cashout');
13
+ * engine.on('balance', ({ balance }) => …);
14
+ * engine.ready(); // first frame is on screen
15
+ *
16
+ * The game never talks to the server. It asks the shell around it, and the
17
+ * shell answers with what the server decided. Show `round` and `balance` as
18
+ * they come back: they are the truth, and nothing the game computes is.
19
+ *
20
+ * A bet is the player's act: call play(), and a ladder's action (advance
21
+ * and cashout alike), from the handler of the player's click or key press,
22
+ * or the shell refuses it with `needs-click`. Autoplay is the shell's: ask
23
+ * for a session (engine.autoplay.start, below, from the player's click too)
24
+ * — the shell shows the player the terms and starts it on their Start,
25
+ * answering you then — play inside it, and follow its `autoplay` events; the
26
+ * shell shows its own Stop over the game.
27
+ */
28
+ import {
29
+ ERRORS,
30
+ GAME_EVENTS,
31
+ REQUESTS,
32
+ SHELL_EVENTS,
33
+ event,
34
+ maxStakeFor,
35
+ readShellMessage,
36
+ request,
37
+ } from './protocol.js';
38
+ import { moneyFor } from './money.js';
39
+
40
+ /** A refusal from the shell or the server. `code` is one of ERRORS. */
41
+ export class EngineError extends Error {
42
+ constructor(code, message, retryAfterMs, round, maxStake) {
43
+ super(message || code);
44
+ this.name = 'EngineError';
45
+ this.code = code;
46
+ if (retryAfterMs > 0) this.retryAfterMs = retryAfterMs;
47
+ // A refusal about a round carries it as it stands now.
48
+ if (round && typeof round === 'object') this.round = round;
49
+ // A `limits` refusal over the maximum win per round carries the most
50
+ // the bet takes (a decimal string in the wallet coin, as the server
51
+ // wrote it): set the field to it.
52
+ if (typeof maxStake === 'string' && maxStake !== '') this.maxStake = maxStake;
53
+ }
54
+ }
55
+
56
+ export { ERRORS };
57
+
58
+ const CONNECT_TIMEOUT_MS = 15_000;
59
+ const HELLO_EVERY_MS = 400;
60
+ // Windows whose uncaught errors are already reported: a second connect() on the same
61
+ // page (a game that reconnects) must not count each error twice.
62
+ const hooked = new WeakSet();
63
+ // The shell always answers: it has its own deadline on the server and turns
64
+ // a timeout into a refusal. This is only a backstop for a shell that is gone.
65
+ const ANSWER_TIMEOUT_MS = 60_000;
66
+
67
+ /**
68
+ * Waits for the shell's `init` and returns the engine handle. Rejects when no
69
+ * shell answers in time (the game was opened outside the shell).
70
+ */
71
+ export function connect({ host = globalThis.parent, self = globalThis, timeoutMs = CONNECT_TIMEOUT_MS } = {}) {
72
+ return new Promise((resolve, reject) => {
73
+ if (!host || host === self) {
74
+ reject(new EngineError(ERRORS.SESSION, 'not inside the engine shell'));
75
+ return;
76
+ }
77
+ // The game says it is here and asks for its config, again every
78
+ // HELLO_EVERY_MS until the shell answers: however long the page took
79
+ // to load, and whenever the shell started listening.
80
+ const hello = () => host.postMessage(event(GAME_EVENTS.HELLO, {}), '*');
81
+ const again = setInterval(hello, HELLO_EVERY_MS);
82
+ const timer = setTimeout(() => {
83
+ clearInterval(again);
84
+ self.removeEventListener('message', onInit);
85
+ reject(new EngineError(ERRORS.SESSION, 'the engine shell did not answer'));
86
+ }, timeoutMs);
87
+ function onInit(e) {
88
+ if (e.source !== host) return;
89
+ const msg = readShellMessage(e.data);
90
+ if (!msg || msg.kind !== 'evt' || msg.type !== SHELL_EVENTS.INIT) return;
91
+ clearTimeout(timer);
92
+ clearInterval(again);
93
+ self.removeEventListener('message', onInit);
94
+ resolve(new Engine(host, self, msg.body));
95
+ }
96
+ self.addEventListener('message', onInit);
97
+ hello();
98
+ });
99
+ }
100
+
101
+ class Engine {
102
+ #host;
103
+ #pending = new Map(); // id -> { resolve, reject, timer }
104
+ #listeners = new Map(); // shell event type -> Set<fn>
105
+ #seq = 0;
106
+ #moneyInFlight = false;
107
+ #balance = null;
108
+ #reveal = null; // { roundId, balance }: the settled round whose result waits for revealed()
109
+
110
+ constructor(host, self, config) {
111
+ this.#host = host;
112
+ this.config = Object.freeze(config);
113
+ // An old shell sends no wallet: the plain coin at its precision.
114
+ const c = config.currency || {};
115
+ this.money = moneyFor(config.wallet || { symbol: c.symbol, name: c.symbol, isFiat: false, rate: 1, betPrecision: c.decimals });
116
+ if (typeof config.balance === 'number') this.#balance = config.balance;
117
+ self.addEventListener('message', (e) => {
118
+ if (e.source !== host) return;
119
+ const msg = readShellMessage(e.data);
120
+ if (!msg) return;
121
+ if (msg.kind === 'res') this.#settle(msg);
122
+ else {
123
+ // The shell's word on the balance outranks a reveal still waiting.
124
+ if (msg.type === SHELL_EVENTS.BALANCE && typeof msg.body.balance === 'number') { this.#balance = msg.body.balance; this.#reveal = null; }
125
+ this.#emit(msg.type, msg.body);
126
+ }
127
+ });
128
+ this.state = Object.freeze({
129
+ get: () => this.#ask(REQUESTS.STATE_GET, {}),
130
+ put: (revision, data) => this.#ask(REQUESTS.STATE_PUT, { revision, data }),
131
+ });
132
+ // Autoplay is a session the shell grants and runs the count of:
133
+ // start({ mode, bet, rounds, stake, stopOnProfit?, stopOnLoss? }) → { session }, on the player's click;
134
+ // rounds ≤ 1000, every play in it on that bet and none above `stake`;
135
+ // a stop ends it at that net result
136
+ // stop() → { stopped: true }, always taken
137
+ // Plays inside it need no click; the shell's `autoplay` event says
138
+ // {active, left, played, reason?} on every change. The shell shows
139
+ // rounds left and Stop over the game itself.
140
+ this.autoplay = Object.freeze({
141
+ start: ({ mode, bet, rounds, stake, stopOnProfit, stopOnLoss } = {}) => {
142
+ const body = { mode, bet, rounds, stake };
143
+ if (stopOnProfit > 0) body.stopOnProfit = stopOnProfit;
144
+ if (stopOnLoss > 0) body.stopOnLoss = stopOnLoss;
145
+ return this.#ask(REQUESTS.AUTOPLAY_START, body);
146
+ },
147
+ stop: () => this.#ask(REQUESTS.AUTOPLAY_STOP, {}),
148
+ });
149
+ // An error the page never caught (a render loop, a click handler, a promise) is
150
+ // reported like reportError: the shell logs it, the engine counts one error, and
151
+ // the studio's Reports show the count. The game's own code sees nothing of this.
152
+ if (!hooked.has(self)) {
153
+ hooked.add(self);
154
+ self.addEventListener('error', (e) => this.reportError(e && e.message ? e.message : 'uncaught error'));
155
+ self.addEventListener('unhandledrejection', (e) => {
156
+ const r = e ? e.reason : undefined;
157
+ this.reportError(r && r.message ? r.message : r === undefined ? 'unhandled rejection' : r);
158
+ });
159
+ }
160
+ }
161
+
162
+ /** The player's balance in the wallet currency, as the site's header
163
+ * shows it: the stake leaves when the bet is placed, the result lands
164
+ * when the game calls revealed(). null while unknown. */
165
+ get balance() {
166
+ return this.#balance;
167
+ }
168
+
169
+ /** Places a bet. Resolves { round, balance? }; rejects with EngineError.
170
+ * The stake is a positive amount: there is no free round (a round that
171
+ * costs nothing would still consume a nonce of the player's seed pair),
172
+ * so 0 is refused here with `limits`, as the server refuses it, before
173
+ * anything is sent. So is a stake over the most this bet may take under
174
+ * the maximum win per round (config.limits.maxStake[mode][bet]): the
175
+ * server would refuse it before the bet, and the refusal names the
176
+ * highest stake the bet takes. A figure of 0 (the coin has no rate yet)
177
+ * is refused as `unavailable`, which is what the server would answer. */
178
+ play({ mode, bet, amount }) {
179
+ if (!(typeof amount === 'number' && Number.isFinite(amount) && amount > 0)) {
180
+ return Promise.reject(new EngineError(ERRORS.LIMITS, 'a stake is required: a round is never played for free'));
181
+ }
182
+ const most = maxStakeFor(this.config.limits, mode, bet);
183
+ if (most === 0) {
184
+ return Promise.reject(new EngineError(ERRORS.UNAVAILABLE, 'nothing can be staked on this bet right now: the coin has no rate to price the maximum win in; try again shortly'));
185
+ }
186
+ // Compared at the wallet's precision, so a float's noise past it never
187
+ // refuses a stake the field shows as exactly the cap.
188
+ if (most !== null && Number(amount.toFixed(this.money.walletDecimals)) > most) {
189
+ const named = this.config.limits.maxStake[mode][bet]; // as the server wrote it (a stand may hand a number)
190
+ return Promise.reject(new EngineError(ERRORS.LIMITS, `the most this bet takes is ${this.money.format(most)} ${this.money.unit}: one round pays no more than the maximum win`, 0, undefined, String(named)));
191
+ }
192
+ return this.#money(REQUESTS.PLAY, { mode, bet, amount }).then((answer) => {
193
+ this.#afterMoney(answer, amount);
194
+ return answer;
195
+ });
196
+ }
197
+
198
+ /** Moves an open ladder round: 'advance' or 'cashout' — either from the player's click
199
+ * (the shell refuses both with `needs-click` otherwise). Pass the round as
200
+ * you last had it: its step goes with the request, so a stale action
201
+ * (another tab, a retry) is refused with the round as it stands. */
202
+ action(round, action) {
203
+ return this.#money(REQUESTS.ACTION, { roundId: round.id, step: round.step, action }).then((answer) => {
204
+ this.#afterMoney(answer, 0);
205
+ return answer;
206
+ });
207
+ }
208
+
209
+ /** Reads one of the player's settled rounds as the game may see it,
210
+ * without its seeds; the shell's dialog (openFairness) holds the receipt. */
211
+ round(roundId) {
212
+ return this.#ask(REQUESTS.ROUND, { roundId });
213
+ }
214
+
215
+ /** Subscribes to a shell event (balance, control, pause, resume, ended, autoplay). */
216
+ on(type, fn) {
217
+ if (!Object.values(SHELL_EVENTS).includes(type) || type === SHELL_EVENTS.INIT) {
218
+ throw new TypeError(`unknown engine event ${type}`);
219
+ }
220
+ if (!this.#listeners.has(type)) this.#listeners.set(type, new Set());
221
+ this.#listeners.get(type).add(fn);
222
+ return () => this.#listeners.get(type).delete(fn);
223
+ }
224
+
225
+ /** The first frame is on screen: the shell drops its loader. */
226
+ ready() {
227
+ this.#tell(GAME_EVENTS.READY, {});
228
+ }
229
+
230
+ /** Progress of an asset group, for the shell's loader and telemetry. */
231
+ loading(group, loaded, total) {
232
+ this.#tell(GAME_EVENTS.LOADING, { group, loaded, total });
233
+ }
234
+
235
+ /** The round's outcome is on screen: the site's header balance follows
236
+ * now. Call it when your reveal animation lands; if you never do, the
237
+ * shell moves the header at its own deadline. */
238
+ revealed(roundId) {
239
+ if (this.#reveal && this.#reveal.roundId === roundId) {
240
+ this.#setBalance(this.#reveal.balance);
241
+ this.#reveal = null;
242
+ }
243
+ this.#tell(GAME_EVENTS.REVEALED, { roundId });
244
+ }
245
+
246
+ /** Opens the shell's fairness dialog on a round. */
247
+ openFairness(roundId) {
248
+ this.#tell(GAME_EVENTS.FAIRNESS, { roundId });
249
+ }
250
+
251
+ /** Reports a game error: the shell logs it in the player's console and the engine counts
252
+ * one error for the game's day. The count reaches the studio; the text never does. */
253
+ reportError(message) {
254
+ this.#tell(GAME_EVENTS.ERROR, { message: String(message).slice(0, 500) });
255
+ }
256
+
257
+ // The header's two steps: the stake leaves now; a settled round's result
258
+ // waits for its reveal, an open round's balance is already the truth.
259
+ #afterMoney(answer, stake) {
260
+ const known = answer && typeof answer.balance === 'number' && answer.balance >= 0 ? answer.balance : null;
261
+ const round = answer && answer.round;
262
+ if (known === null) return;
263
+ // A reveal the game never reported is overtaken by the next round.
264
+ if (this.#reveal) this.#setBalance(this.#reveal.balance);
265
+ this.#reveal = null;
266
+ if (round && round.state !== 'open' && typeof round.id === 'string') {
267
+ this.#reveal = { roundId: round.id, balance: known };
268
+ const dp = this.money.walletDecimals;
269
+ if (this.#balance !== null && stake > 0) this.#setBalance(Math.max(0, Number((this.#balance - stake).toFixed(dp))));
270
+ } else {
271
+ this.#setBalance(known);
272
+ }
273
+ }
274
+
275
+ #setBalance(balance) {
276
+ if (balance === this.#balance) return;
277
+ this.#balance = balance;
278
+ this.#emit(SHELL_EVENTS.BALANCE, { balance });
279
+ }
280
+
281
+ // One bet or action at a time: the server would refuse the second one
282
+ // anyway (it holds the player's lock), and saying so here is clearer.
283
+ #money(type, body) {
284
+ if (this.#moneyInFlight) {
285
+ return Promise.reject(new EngineError(ERRORS.BUSY, 'another bet is still with the server'));
286
+ }
287
+ this.#moneyInFlight = true;
288
+ return this.#ask(type, body).finally(() => {
289
+ this.#moneyInFlight = false;
290
+ });
291
+ }
292
+
293
+ #ask(type, body) {
294
+ const id = `r${++this.#seq}`;
295
+ return new Promise((resolve, reject) => {
296
+ const timer = setTimeout(() => {
297
+ this.#pending.delete(id);
298
+ reject(new EngineError(ERRORS.SERVER, 'the engine shell did not answer'));
299
+ }, ANSWER_TIMEOUT_MS);
300
+ this.#pending.set(id, { resolve, reject, timer });
301
+ this.#host.postMessage(request(id, type, body), '*');
302
+ });
303
+ }
304
+
305
+ #settle(msg) {
306
+ const p = this.#pending.get(msg.id);
307
+ if (!p) return;
308
+ this.#pending.delete(msg.id);
309
+ clearTimeout(p.timer);
310
+ if (msg.ok) p.resolve(msg.body);
311
+ else p.reject(new EngineError(msg.error.code, msg.error.message, msg.error.retryAfterMs, msg.error.round, msg.error.maxStake));
312
+ }
313
+
314
+ #emit(type, body) {
315
+ for (const fn of this.#listeners.get(type) ?? []) {
316
+ try {
317
+ fn(body);
318
+ } catch (err) {
319
+ this.reportError(err && err.message ? err.message : err);
320
+ }
321
+ }
322
+ }
323
+
324
+ // The shell's window is the only reader. '*' is the target because the
325
+ // game cannot name the shell's origin from inside an opaque sandbox; the
326
+ // message carries nothing secret.
327
+ #tell(type, body) {
328
+ this.#host.postMessage(event(type, body), '*');
329
+ }
330
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Why a stake cannot be played now, as the bet field (bet-field.js) and the
3
+ * engine panel (ui.js) decide it before anything reaches the shell. One pure
4
+ * rule, so it is tested without a browser:
5
+ *
6
+ * 'balance' over the balance the shell last reported
7
+ * 'min' no stake at all, or under the coin's minimum — an engine round
8
+ * is never played for free (SPEC §2 item 5): a round costs a
9
+ * nonce of the player's seed pair, so the server refuses a stake
10
+ * of 0 and the field never offers one
11
+ * 'max' over the coin's maximum
12
+ * 'maxWin' over the most this bet may take under the maximum win per
13
+ * round (SPEC §3.1): stake × the bet's top multiplier would pass
14
+ * the cap, so the server would refuse the bet before it starts.
15
+ * A ceiling of 0 (the server names it while the coin has no USD
16
+ * rate) refuses every stake, the empty field included: nothing
17
+ * can be staked on the bet until the rate is back
18
+ * null playable
19
+ *
20
+ * Of the coin's maximum and the bet's own ceiling, the lower is the one named:
21
+ * the server's `maxStake` never passes the coin's max bet, so a player who
22
+ * types more than both hears the figure the bet can take, not one it never
23
+ * could. `limits` is engine.config.limits ({min, max} in wallet units; either
24
+ * may be missing, as in the sandbox); `balance` is engine.balance (null while
25
+ * unknown); `maxStake` is the selected bet's own ceiling in wallet units
26
+ * (protocol.js maxStakeFor), or null when the session names none for it.
27
+ */
28
+ export function stakeReason(amount, limits, balance, maxStake = null) {
29
+ const a = Number(amount);
30
+ const { min, max } = limits || {};
31
+ const coinMax = typeof max === 'number' && max > 0 ? max : null;
32
+ const betMax = typeof maxStake === 'number' && maxStake >= 0 ? maxStake : null;
33
+ if (betMax === 0) return 'maxWin';
34
+ if (!(a > 0) || !Number.isFinite(a)) return 'min';
35
+ if (balance !== null && balance !== undefined && a > balance) return 'balance';
36
+ if (typeof min === 'number' && a < min) return 'min';
37
+ // The lower ceiling first; a bet's ceiling above the coin's (a stand) is
38
+ // never reached before the coin's is.
39
+ if (betMax !== null && (coinMax === null || betMax <= coinMax) && a > betMax) return 'maxWin';
40
+ if (coinMax !== null && a > coinMax) return 'max';
41
+ return null;
42
+ }
43
+
44
+ /**
45
+ * The most the field offers (its max chip): the coin's maximum, the selected
46
+ * bet's own ceiling under the maximum win and the balance, whichever is
47
+ * least; Infinity when none is known. A ceiling of 0 is 0: nothing to offer.
48
+ */
49
+ export function stakeCeiling(limits, balance, maxStake = null) {
50
+ const { max } = limits || {};
51
+ return Math.min(
52
+ typeof max === 'number' && max > 0 ? max : Infinity,
53
+ typeof maxStake === 'number' && maxStake >= 0 ? maxStake : Infinity,
54
+ balance ?? Infinity,
55
+ );
56
+ }
57
+
58
+ /**
59
+ * The least a player can stake: the coin's minimum when the server named
60
+ * one, else one unit of the wallet's precision — what the "minimum bet"
61
+ * message says when the field is empty.
62
+ */
63
+ export function leastStake(limits, walletDecimals) {
64
+ const { min } = limits || {};
65
+ if (typeof min === 'number' && min > 0) return min;
66
+ const dp = Number.isInteger(walletDecimals) && walletDecimals >= 0 ? walletDecimals : 8;
67
+ return 10 ** -dp;
68
+ }
@@ -0,0 +1,36 @@
1
+ import type { Engine, Round } from './sdk.js';
2
+
3
+ export interface Game {
4
+ readonly engine: Engine;
5
+ /** The stake field (<hb-bet-amount>). `reason` says why the stake cannot be played: over the balance,
6
+ * under the coin's minimum, over its maximum, or over the most the selected bet takes under the
7
+ * maximum win per round (`maxWin`); the panel keeps `bet` on the selection. */
8
+ readonly field: HTMLElement & {
9
+ amount: number;
10
+ reason: { code: 'balance' | 'min' | 'max' | 'maxWin'; message: string } | null;
11
+ disabled: boolean;
12
+ bet: { mode: string; bet: string } | null;
13
+ /** The most the selected bet takes, in wallet units; null when the session names none;
14
+ * 0 when nothing can be staked for now (the coin has no rate). */
15
+ readonly maxStake: number | null;
16
+ };
17
+ mode: string;
18
+ bet: string;
19
+ /** The ladder round in play, if any. */
20
+ readonly open: Round | null;
21
+ /** Climb or cash out the open ladder round, as the panel's buttons do (a zone that drives its own ladder). */
22
+ advance(): Promise<Round | null>;
23
+ cashout(): Promise<Round | null>;
24
+ }
25
+
26
+ /** The engine panel around your zone: modes, bets, stake, Bet, Auto, Next / Cash out. */
27
+ export function mount(el: HTMLElement, hooks: {
28
+ /** A table round is drawn: show it; resolve when it is on screen. */
29
+ reveal?: (round: Round, zone: HTMLElement) => Promise<void> | void;
30
+ /** A ladder round moved (bet, step, cash-out, bust). */
31
+ step?: (round: Round, zone: HTMLElement) => Promise<void> | void;
32
+ /** false: no Next button; the zone climbs with game.advance(). */
33
+ next?: boolean;
34
+ /** Mode and bet names by language, then id: { es: { base: 'Tirada' } } (else the math's labels). */
35
+ labels?: Record<string, Record<string, string>>;
36
+ }): Promise<Game>;