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,681 @@
1
+ /**
2
+ * The sandbox-only test controls. boot.js imports this module only after the
3
+ * round server reports env === 'sandbox', so the casino never creates this UI.
4
+ * Maths and money still come from the shell's existing owners; this module only
5
+ * presents controls and sends their domain inputs to those owners.
6
+ */
7
+ import { rowLabel, times } from './fair.js';
8
+ import { loadPack } from './engine-v1.js';
9
+ import { applicableScenarios, createScenarioContext, createScenarioRunner, SCENARIO_TIMEOUT } from './sandbox-scenarios.js';
10
+
11
+ function text(tag, value) {
12
+ const element = document.createElement(tag);
13
+ element.textContent = value;
14
+ return element;
15
+ }
16
+
17
+ function labelOf(value, fallback) {
18
+ return value && typeof value.label === 'string' && value.label ? value.label : fallback;
19
+ }
20
+
21
+ function rangesContain(ranges, index) {
22
+ return ranges.some((range) => Array.isArray(range) && index >= range[0] && index <= range[1]);
23
+ }
24
+
25
+ function firstRowInRange(ranges, rows) {
26
+ for (const range of Array.isArray(ranges) ? ranges : []) {
27
+ if (!Array.isArray(range) || !Number.isInteger(range[0])) continue;
28
+ if (range[0] >= 0 && range[0] < rows.length) return range[0];
29
+ }
30
+ return null;
31
+ }
32
+
33
+ function firstBustRow(ranges, rows) {
34
+ for (let index = 0; index < rows.length; index += 1) {
35
+ if (!rangesContain(ranges, index)) return index;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function uniqueLabel(label, used) {
41
+ if (!used.has(label)) {
42
+ used.add(label);
43
+ return label;
44
+ }
45
+ let suffix = 2;
46
+ let candidate = `${label} · ${suffix}`;
47
+ while (used.has(candidate)) {
48
+ suffix += 1;
49
+ candidate = `${label} · ${suffix}`;
50
+ }
51
+ used.add(candidate);
52
+ return candidate;
53
+ }
54
+
55
+ function subjectLabel(mode, bet, modes, bets) {
56
+ const parts = [];
57
+ if (modes > 1) parts.push(labelOf(mode, mode.id));
58
+ if (bets > 1) parts.push(labelOf(bet, bet.id));
59
+ return parts.join(' · ');
60
+ }
61
+
62
+ function entryLabel(mode, bet, modes, bets, suffix) {
63
+ const subject = subjectLabel(mode, bet, modes, bets);
64
+ return subject ? `${subject} · ${suffix}` : suffix;
65
+ }
66
+
67
+ /** Returns the forceable rows represented by a parsed maths pack. */
68
+ export function tierEntries(pack) {
69
+ const modes = Array.isArray(pack?.manifest?.modes) ? pack.manifest.modes : [];
70
+ const entries = [];
71
+ const used = new Set();
72
+ for (const mode of modes) {
73
+ const bets = Array.isArray(mode.bets) ? mode.bets : [];
74
+ for (const bet of bets) {
75
+ if (mode.kind === 'table') {
76
+ const seen = new Set();
77
+ const tiers = Array.isArray(bet.tiers) ? bet.tiers : [];
78
+ for (const tier of tiers) {
79
+ if (!Number.isSafeInteger(tier.multBp) || seen.has(tier.multBp)) continue;
80
+ const rowIndex = firstRowInRange(tier.outcomes, pack.tables[mode.table]?.rows || []);
81
+ if (rowIndex === null) continue;
82
+ seen.add(tier.multBp);
83
+ const suffix = times(tier.multBp);
84
+ entries.push({
85
+ indices: [rowIndex],
86
+ mode: mode.id,
87
+ bet: bet.id,
88
+ modeLabel: labelOf(mode, mode.id),
89
+ betLabel: labelOf(bet, bet.id),
90
+ autoAdvance: false,
91
+ multiplier: tier.multBp,
92
+ subject: subjectLabel(mode, bet, modes.length, bets.length),
93
+ label: uniqueLabel(entryLabel(mode, bet, modes.length, bets.length, suffix), used),
94
+ detail: rowLabel(pack, { table: mode.table, index: rowIndex }),
95
+ });
96
+ }
97
+ continue;
98
+ }
99
+ if (mode.kind !== 'ladder') continue;
100
+ const steps = Array.isArray(mode.steps) ? mode.steps : [];
101
+ const survive = [];
102
+ let canSurvive = true;
103
+ for (let step = 0; step < steps.length; step += 1) {
104
+ const table = pack.tables[steps[step].table];
105
+ const row = firstRowInRange([bet.survive?.[step]], table?.rows || []);
106
+ if (row === null) canSurvive = false;
107
+ survive.push(row);
108
+ }
109
+ if (canSurvive && survive.length === steps.length && survive.length > 0) {
110
+ entries.push({
111
+ indices: survive,
112
+ mode: mode.id,
113
+ bet: bet.id,
114
+ modeLabel: labelOf(mode, mode.id),
115
+ betLabel: labelOf(bet, bet.id),
116
+ autoAdvance: mode.autoAdvance === true,
117
+ multiplier: null,
118
+ subject: subjectLabel(mode, bet, modes.length, bets.length),
119
+ label: uniqueLabel(entryLabel(mode, bet, modes.length, bets.length, 'Survive every step'), used),
120
+ detail: survive.map((row, step) => rowLabel(pack, { table: steps[step].table, index: row })).join(' · '),
121
+ });
122
+ }
123
+ const firstTable = pack.tables[steps[0]?.table];
124
+ const firstRanges = bet.survive?.[0] || [];
125
+ const bust = firstBustRow(firstRanges, firstTable?.rows || []);
126
+ const bustIndices = [bust];
127
+ for (let step = 1; bust !== null && step < steps.length; step += 1) {
128
+ const table = pack.tables[steps[step].table];
129
+ if (!table?.rows?.length) bustIndices.length = 0;
130
+ else bustIndices.push(0);
131
+ }
132
+ if (bust !== null && bustIndices.length === steps.length) {
133
+ entries.push({
134
+ indices: bustIndices,
135
+ mode: mode.id,
136
+ bet: bet.id,
137
+ modeLabel: labelOf(mode, mode.id),
138
+ betLabel: labelOf(bet, bet.id),
139
+ autoAdvance: mode.autoAdvance === true,
140
+ multiplier: null,
141
+ subject: subjectLabel(mode, bet, modes.length, bets.length),
142
+ label: uniqueLabel(entryLabel(mode, bet, modes.length, bets.length, 'Bust at step 1'), used),
143
+ detail: bustIndices.map((row, step) => rowLabel(pack, { table: steps[step].table, index: row })).join(' · '),
144
+ });
145
+ }
146
+ }
147
+ }
148
+ return entries;
149
+ }
150
+
151
+ /** Returns no controls when the document is not the requested committed pack. */
152
+ export function entriesForVersion(pack, version) {
153
+ if (!pack || pack.recomputed !== version) return [];
154
+ const entries = tierEntries(pack);
155
+ const used = new Set(entries.map((entry) => entry.label));
156
+ const modes = Array.isArray(pack.manifest?.modes) ? pack.manifest.modes : [];
157
+ for (const mode of modes) {
158
+ if (mode.kind !== 'table') continue;
159
+ const rows = pack.tables?.[mode.table]?.rows || [];
160
+ for (const bet of Array.isArray(mode.bets) ? mode.bets : []) {
161
+ const covered = (Array.isArray(bet.tiers) ? bet.tiers : []).flatMap((tier) => Array.isArray(tier.outcomes) ? tier.outcomes : []);
162
+ const losing = firstBustRow(covered, rows);
163
+ if (losing === null) continue;
164
+ const subject = subjectLabel(mode, bet, modes.length, mode.bets.length);
165
+ entries.push({
166
+ indices: [losing],
167
+ mode: mode.id,
168
+ bet: bet.id,
169
+ modeLabel: labelOf(mode, mode.id),
170
+ betLabel: labelOf(bet, bet.id),
171
+ autoAdvance: false,
172
+ multiplier: 0,
173
+ loss: true,
174
+ subject,
175
+ label: uniqueLabel(entryLabel(mode, bet, modes.length, mode.bets.length, 'Losing row'), used),
176
+ detail: rowLabel(pack, { table: mode.table, index: losing }),
177
+ });
178
+ }
179
+ }
180
+ return entries;
181
+ }
182
+
183
+ /** Converts a displayed amount to the server's plain decimal wallet string. */
184
+ export function balanceValue(value, money) {
185
+ if (!/^\d+(\.\d+)?$/.test(value)) return null;
186
+ const display = money.parse(value);
187
+ const amount = display;
188
+ if (!Number.isFinite(amount) || amount < 0 || amount > 1e9) return null;
189
+ const decimals = Math.min(8, Number.isInteger(money.walletDecimals) ? money.walletDecimals : 8);
190
+ const decimal = amount.toFixed(decimals).replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '');
191
+ if (!/^\d{1,10}(\.\d{1,8})?$/.test(decimal)) return null;
192
+ return { amount, decimal };
193
+ }
194
+
195
+ function addTierButtons(box, entries, rgs, status, register) {
196
+ for (const tier of entries) {
197
+ const button = document.createElement('button');
198
+ button.type = 'button';
199
+ button.className = 'sandbox-test-tier';
200
+ button.textContent = tier.label;
201
+ button.title = tier.detail;
202
+ button.addEventListener('click', async () => {
203
+ button.disabled = true;
204
+ try {
205
+ const result = await rgs.force(tier.indices, tier.mode);
206
+ const label = tier.multiplier === null ? tier.label : times(tier.multiplier);
207
+ const subject = tier.subject ? `Next ${tier.subject} round: ` : 'Next round forced: ';
208
+ status.textContent = result.ok ? subject + label : (result.error?.message || 'The next round was refused.');
209
+ } finally {
210
+ button.disabled = runActive;
211
+ }
212
+ });
213
+ box.append(button);
214
+ register?.(button);
215
+ }
216
+ }
217
+
218
+ /** Creates the panel over the game frame for a sandbox session. */
219
+ export function createSandboxPanel({ rgs, router, faults, api, slug, mathHash, frame, money, refresh }) {
220
+ if (typeof document === 'undefined' || !rgs || !router || !faults) return null;
221
+ const stage = document.getElementById('stage');
222
+ if (!stage) return null;
223
+
224
+ const root = document.createElement('div');
225
+ root.className = 'sandbox-test';
226
+ const toggle = document.createElement('button');
227
+ toggle.type = 'button';
228
+ toggle.className = 'sandbox-test-toggle';
229
+ toggle.textContent = 'Test';
230
+ toggle.setAttribute('aria-expanded', 'false');
231
+ toggle.setAttribute('aria-controls', 'sandbox-test-panel');
232
+ const panel = document.createElement('section');
233
+ panel.id = 'sandbox-test-panel';
234
+ panel.className = 'sandbox-test-panel';
235
+ panel.hidden = true;
236
+ panel.setAttribute('aria-label', 'Sandbox test controls');
237
+ const status = text('p', '');
238
+ status.className = 'sandbox-test-status';
239
+ status.setAttribute('role', 'status');
240
+ status.setAttribute('aria-live', 'polite');
241
+ const instruction = text('p', '');
242
+ instruction.className = 'sandbox-test-instruction';
243
+ instruction.hidden = true;
244
+ instruction.setAttribute('role', 'status');
245
+ const observations = [];
246
+ const maxObservations = 64;
247
+ const waiters = new Set();
248
+ let observationSequence = 0;
249
+ let scenarioStartSequence = 0;
250
+ let scenarioStartAt = 0;
251
+ if (typeof router.observe === 'function') router.observe((observation) => {
252
+ const observed = { ...observation, observedAt: Date.now(), observationSequence: ++observationSequence };
253
+ if (!runActive) return;
254
+ observations.push(observed);
255
+ if (observations.length > maxObservations) observations.splice(0, observations.length - maxObservations);
256
+ for (const waiter of [...waiters]) {
257
+ if (waiter.skip) continue;
258
+ let matched = false;
259
+ try {
260
+ matched = observed.observationSequence > waiter.startSequence && observed.observedAt >= waiter.startAt && waiter.predicate(observed);
261
+ } catch {
262
+ matched = false;
263
+ }
264
+ if (matched) {
265
+ waiters.delete(waiter);
266
+ clearTimeout(waiter.timer);
267
+ const index = observations.indexOf(observed);
268
+ if (index >= 0) observations.splice(index, 1);
269
+ waiter.resolve(observed);
270
+ }
271
+ }
272
+ });
273
+ const waitFor = (predicate, ms = 90_000, registerCancel) => {
274
+ const startSequence = scenarioStartSequence;
275
+ const startAt = scenarioStartAt;
276
+ for (let index = 0; index < observations.length; index += 1) {
277
+ const observation = observations[index];
278
+ let matched = false;
279
+ try {
280
+ matched = observation.observationSequence > startSequence && observation.observedAt >= startAt && predicate(observation);
281
+ } catch {
282
+ matched = false;
283
+ }
284
+ if (matched) {
285
+ observations.splice(index, 1);
286
+ return Promise.resolve(observation);
287
+ }
288
+ }
289
+ return new Promise((resolve) => {
290
+ const waiter = {
291
+ predicate,
292
+ resolve,
293
+ skip: false,
294
+ startSequence,
295
+ startAt,
296
+ timer: setTimeout(() => {
297
+ waiters.delete(waiter);
298
+ resolve(SCENARIO_TIMEOUT);
299
+ }, ms),
300
+ };
301
+ registerCancel?.(() => {
302
+ waiters.delete(waiter);
303
+ clearTimeout(waiter.timer);
304
+ waiter.skip = true;
305
+ resolve(null);
306
+ });
307
+ waiters.add(waiter);
308
+ });
309
+ };
310
+ let loaded = false;
311
+ toggle.addEventListener('click', () => {
312
+ panel.hidden = !panel.hidden;
313
+ toggle.setAttribute('aria-expanded', String(!panel.hidden));
314
+ if (!panel.hidden && !loaded) {
315
+ loaded = true;
316
+ loadPack(api, slug, mathHash).then((pack) => {
317
+ if (pack.recomputed !== mathHash) {
318
+ status.textContent = 'The maths pack does not match this version.';
319
+ return;
320
+ }
321
+ scenarioEntries = entriesForVersion(pack, mathHash);
322
+ addTierButtons(tierBox, scenarioEntries, rgs, status, (button) => manualControls.push(button));
323
+ addScenarioControls(scenariosBox);
324
+ }, (error) => {
325
+ status.textContent = error.message || 'Maths could not be loaded.';
326
+ });
327
+ }
328
+ if (!panel.hidden) panel.querySelector('button, input')?.focus();
329
+ });
330
+ root.append(toggle, panel);
331
+ stage.append(root);
332
+
333
+ panel.append(text('h2', 'Sandbox tests'), instruction);
334
+ const rounds = document.createElement('div');
335
+ rounds.className = 'sandbox-test-section';
336
+ rounds.append(text('h3', 'Next round'));
337
+ const tierBox = document.createElement('div');
338
+ tierBox.className = 'sandbox-test-tiers';
339
+ rounds.append(tierBox);
340
+ panel.append(rounds);
341
+ const scenariosSection = document.createElement('div');
342
+ scenariosSection.className = 'sandbox-test-section sandbox-test-scenarios-section';
343
+ scenariosSection.append(text('h3', 'Scenarios'));
344
+ const scenariosBox = document.createElement('div');
345
+ scenariosBox.className = 'sandbox-test-scenarios';
346
+ const runAll = document.createElement('button');
347
+ runAll.type = 'button';
348
+ runAll.textContent = 'Run all';
349
+ const skip = document.createElement('button');
350
+ skip.type = 'button';
351
+ skip.textContent = 'Skip';
352
+ skip.hidden = true;
353
+ const stop = document.createElement('button');
354
+ stop.type = 'button';
355
+ stop.textContent = 'Stop';
356
+ stop.hidden = true;
357
+ const summary = document.createElement('ul');
358
+ summary.className = 'sandbox-test-summary';
359
+ scenariosSection.append(scenariosBox, runAll, skip, stop, summary);
360
+ panel.append(scenariosSection);
361
+ let scenarioEntries = [];
362
+ let scenarioButtons = [];
363
+ const manualControls = [];
364
+ let runActive = false;
365
+ let activeRunner = null;
366
+ const setRunningControls = (disabled) => {
367
+ for (const control of [...manualControls, ...scenarioButtons]) control.disabled = disabled;
368
+ runAll.disabled = disabled || scenarioButtons.length === 0;
369
+ };
370
+ let summaryItems = [];
371
+ let verdictResolve = null;
372
+ const renderSummary = (items) => {
373
+ summary.replaceChildren();
374
+ for (const { scenario, outcome } of items) {
375
+ const line = document.createElement('li');
376
+ const label = outcome.status === 'expected'
377
+ ? 'as expected'
378
+ : outcome.status === 'needs-verdict'
379
+ ? `needs verdict (${outcome.detail})`
380
+ : outcome.status === 'skipped'
381
+ ? 'skipped'
382
+ : outcome.status === 'stopped'
383
+ ? 'stopped'
384
+ : `different (${outcome.detail})`;
385
+ const warnings = outcome.warnings?.length ? ` · warnings: ${outcome.warnings.join('; ')}` : '';
386
+ line.textContent = `${scenario.title}: ${label}${outcome.verdict ? ` · ${outcome.verdict}` : ''}${warnings}`;
387
+ if (outcome.status === 'needs-verdict' && !outcome.verdict) {
388
+ const right = document.createElement('button');
389
+ right.type = 'button';
390
+ right.textContent = 'Looks right';
391
+ const wrong = document.createElement('button');
392
+ wrong.type = 'button';
393
+ wrong.textContent = 'Looks wrong';
394
+ const skipVerdict = document.createElement('button');
395
+ skipVerdict.type = 'button';
396
+ skipVerdict.textContent = 'Skip verdict';
397
+ right.addEventListener('click', () => finishReview('looks right'));
398
+ wrong.addEventListener('click', () => finishReview('looks wrong'));
399
+ skipVerdict.addEventListener('click', () => finishReview('skip verdict'));
400
+ line.append(' ', right, ' ', wrong, ' ', skipVerdict);
401
+ }
402
+ summary.append(line);
403
+ }
404
+ };
405
+ const scenarioContext = () => createScenarioContext({
406
+ entries: scenarioEntries,
407
+ limits: router.init?.()?.limits || {},
408
+ walletDecimals: money.walletDecimals,
409
+ setBalance: (value) => setWalletBalance(Number(value)),
410
+ force: (indices, mode) => rgs.force(indices, mode),
411
+ clearForce: () => rgs.clearForce(),
412
+ armFault: (kind, options = {}) => faults.arm(kind, options),
413
+ clearFaults: () => faults.clear(),
414
+ reload: async () => {
415
+ const result = await refresh();
416
+ if (result.ok) frame.src = frame.src;
417
+ return result;
418
+ },
419
+ currentBalance: async () => {
420
+ const response = await rgs.balance();
421
+ const value = response?.ok && typeof response.body?.balance === 'number' ? response.body.balance : undefined;
422
+ return value;
423
+ },
424
+ startingBalance: typeof router.init?.()?.balance === 'number' ? router.init().balance : undefined,
425
+ say: (message) => { status.textContent = message; if (runActive) { instruction.hidden = false; instruction.textContent = message; } },
426
+ waitFor: (predicate, ms, registerCancel) => waitFor(predicate, ms, registerCancel),
427
+ beforeScenario: () => {
428
+ observations.length = 0;
429
+ scenarioStartSequence = observationSequence;
430
+ scenarioStartAt = Date.now();
431
+ },
432
+ beforeWait: () => {
433
+ observations.length = 0;
434
+ scenarioStartSequence = observationSequence;
435
+ scenarioStartAt = Date.now();
436
+ },
437
+ awaitVerdict: (item) => new Promise((resolve) => {
438
+ reviewOutcome = item;
439
+ verdictResolve = resolve;
440
+ looksRight.hidden = false;
441
+ looksWrong.hidden = false;
442
+ renderSummary(summaryItems);
443
+ }),
444
+ cancelVerdict: () => {
445
+ verdictResolve?.();
446
+ verdictResolve = null;
447
+ },
448
+ });
449
+ let reviewOutcome = null;
450
+ let looksRight;
451
+ let looksWrong;
452
+ const finishReview = (verdict) => {
453
+ if (!reviewOutcome) return;
454
+ reviewOutcome.outcome.verdict = verdict;
455
+ reviewOutcome.outcome.status = verdict === 'looks right' ? 'expected' : verdict === 'looks wrong' ? 'different' : 'skipped';
456
+ looksRight.hidden = true;
457
+ looksWrong.hidden = true;
458
+ status.textContent = `${reviewOutcome.scenario.title}: ${reviewOutcome.outcome.detail} · ${verdict}`;
459
+ renderSummary(summaryItems);
460
+ verdictResolve?.();
461
+ verdictResolve = null;
462
+ };
463
+ const runOne = async (scenario) => {
464
+ runActive = true;
465
+ root.classList.add('sandbox-test-running');
466
+ setRunningControls(true);
467
+ skip.hidden = false;
468
+ stop.hidden = false;
469
+ looksRight?.setAttribute('hidden', '');
470
+ looksWrong?.setAttribute('hidden', '');
471
+ instruction.hidden = false;
472
+ instruction.textContent = scenario.todo;
473
+ status.textContent = `${scenario.title} — ${scenario.todo}`;
474
+ activeRunner = createScenarioRunner({ scenarios: [scenario], ctx: scenarioContext() });
475
+ try {
476
+ const outcome = await activeRunner.run(scenario);
477
+ const stoppedAt = activeRunner.wasStopped();
478
+ status.textContent = stoppedAt ? `Stopped at ${stoppedAt}/1` : `${scenario.title}: ${outcome.detail}`;
479
+ reviewOutcome = { scenario, outcome };
480
+ summaryItems = [reviewOutcome];
481
+ renderSummary(summaryItems);
482
+ looksRight.hidden = !outcome.review;
483
+ looksWrong.hidden = !outcome.review;
484
+ return outcome;
485
+ } finally {
486
+ runActive = false;
487
+ root.classList.remove('sandbox-test-running');
488
+ setRunningControls(false);
489
+ skip.hidden = true;
490
+ stop.hidden = true;
491
+ instruction.hidden = true;
492
+ activeRunner = null;
493
+ }
494
+ };
495
+ function addScenarioControls(box) {
496
+ box.replaceChildren();
497
+ scenarioButtons = [];
498
+ const ctx = scenarioContext();
499
+ const applicable = applicableScenarios(ctx);
500
+ const groups = new Map();
501
+ for (const scenario of applicable) {
502
+ if (!groups.has(scenario.group)) {
503
+ const group = document.createElement('div');
504
+ group.className = 'sandbox-test-scenario-group';
505
+ group.append(text('strong', scenario.group));
506
+ box.append(group);
507
+ groups.set(scenario.group, group);
508
+ }
509
+ const button = document.createElement('button');
510
+ button.type = 'button';
511
+ button.textContent = scenario.title;
512
+ button.title = scenario.look;
513
+ button.addEventListener('click', () => { if (!runActive) runOne(scenario); });
514
+ groups.get(scenario.group).append(button);
515
+ scenarioButtons.push(button);
516
+ manualControls.push(button);
517
+ }
518
+ runAll.disabled = applicable.length === 0;
519
+ runAll.onclick = async () => {
520
+ if (runActive) return;
521
+ runActive = true;
522
+ root.classList.add('sandbox-test-running');
523
+ setRunningControls(true);
524
+ skip.hidden = false;
525
+ stop.hidden = false;
526
+ looksRight?.setAttribute('hidden', '');
527
+ looksWrong?.setAttribute('hidden', '');
528
+ summaryItems = [];
529
+ summary.replaceChildren();
530
+ activeRunner = createScenarioRunner({
531
+ scenarios: applicable,
532
+ ctx: scenarioContext(),
533
+ onScenario: (item) => {
534
+ summaryItems.push({ scenario: item.scenario, outcome: item.outcome });
535
+ renderSummary(summaryItems);
536
+ },
537
+ });
538
+ instruction.hidden = false;
539
+ try {
540
+ const results = await activeRunner.runAll();
541
+ renderSummary(results);
542
+ const stoppedAt = activeRunner.wasStopped();
543
+ const warning = results.at(-1)?.outcome?.warnings?.join('; ');
544
+ status.textContent = warning ? `Stopped at ${stoppedAt}/${applicable.length}: ${warning}` : stoppedAt ? `Stopped at ${stoppedAt}/${applicable.length}` : `Finished ${results.length} / ${applicable.length}`;
545
+ } finally {
546
+ runActive = false;
547
+ root.classList.remove('sandbox-test-running');
548
+ setRunningControls(false);
549
+ skip.hidden = true;
550
+ stop.hidden = true;
551
+ instruction.hidden = true;
552
+ activeRunner = null;
553
+ }
554
+ };
555
+ }
556
+ skip.addEventListener('click', () => activeRunner?.skip());
557
+ stop.addEventListener('click', () => activeRunner?.stop());
558
+ const balance = document.createElement('div');
559
+ balance.className = 'sandbox-test-section';
560
+ balance.append(text('h3', 'Balance'));
561
+ const input = document.createElement('input');
562
+ input.type = 'text';
563
+ input.inputMode = 'decimal';
564
+ input.setAttribute('aria-label', 'Balance amount');
565
+ input.placeholder = money?.unit ? `Amount (${money.unit})` : 'Amount';
566
+ const set = document.createElement('button');
567
+ set.type = 'button';
568
+ set.textContent = 'Set';
569
+ const empty = document.createElement('button');
570
+ empty.type = 'button';
571
+ empty.textContent = 'Empty';
572
+ const setBalanceValue = async (value) => {
573
+ const parsed = balanceValue(value, money);
574
+ if (!parsed) {
575
+ status.textContent = /^\d+(\.\d+)?$/.test(value) && Number(value) > 1e9 ? 'At most 1,000,000,000' : 'Enter a valid non-negative amount.';
576
+ return;
577
+ }
578
+ set.disabled = true;
579
+ empty.disabled = true;
580
+ try {
581
+ const result = await rgs.sandboxBalance(parsed.decimal);
582
+ if (!result.ok) {
583
+ status.textContent = result.error?.message || 'Balance refused.';
584
+ return;
585
+ }
586
+ const balance = result.body && result.body.balance;
587
+ if (typeof balance !== 'number' || !Number.isFinite(balance) || balance < 0) {
588
+ status.textContent = 'Balance response was invalid.';
589
+ return;
590
+ }
591
+ router.balanceChanged(balance);
592
+ status.textContent = `Balance set: ${money.format(balance)}`;
593
+ } finally {
594
+ set.disabled = runActive;
595
+ empty.disabled = runActive;
596
+ }
597
+ };
598
+ const setWalletBalance = async (value) => {
599
+ if (!Number.isFinite(value) || value < 0) return { ok: false, error: { message: 'Balance is invalid.' } };
600
+ const decimals = Math.min(8, Number.isInteger(money?.walletDecimals) ? money.walletDecimals : 8);
601
+ const decimal = value.toFixed(decimals).replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '') || '0';
602
+ const result = await rgs.sandboxBalance(decimal);
603
+ if (result.ok) {
604
+ const balance = result.body && result.body.balance;
605
+ if (typeof balance === 'number' && Number.isFinite(balance) && balance >= 0) {
606
+ router.balanceChanged(balance);
607
+ }
608
+ }
609
+ return result;
610
+ };
611
+ set.addEventListener('click', () => setBalanceValue(input.value.trim()));
612
+ empty.addEventListener('click', () => {
613
+ input.value = '0';
614
+ setBalanceValue('0');
615
+ });
616
+ balance.append(input, set, empty);
617
+ manualControls.push(input, set, empty);
618
+ panel.append(balance);
619
+
620
+ const faultsSection = document.createElement('div');
621
+ faultsSection.className = 'sandbox-test-section';
622
+ faultsSection.append(text('h3', 'Faults'));
623
+ const showFaultState = () => {
624
+ const state = faults.state();
625
+ status.textContent = state ? `${state.kind} armed for ${state.times} call${state.times === 1 ? '' : 's'}` : 'No fault armed';
626
+ };
627
+ faults.onChange?.(showFaultState);
628
+ for (const kind of ['session', 'server', 'unavailable', 'timeout', 'slow']) {
629
+ const row = document.createElement('div');
630
+ row.className = 'sandbox-test-fault';
631
+ const name = text('span', kind);
632
+ const note = kind === 'session' ? text('small', 'the game is told its session expired; reopen from House Studio to play on') : null;
633
+ const one = document.createElement('button');
634
+ one.type = 'button';
635
+ one.textContent = 'Next call';
636
+ const three = document.createElement('button');
637
+ three.type = 'button';
638
+ three.textContent = 'Next 3 calls';
639
+ one.addEventListener('click', () => {
640
+ faults.arm(kind, { times: 1 });
641
+ });
642
+ three.addEventListener('click', () => {
643
+ faults.arm(kind, { times: 3 });
644
+ });
645
+ row.append(name);
646
+ if (note) row.append(note);
647
+ row.append(one, three);
648
+ faultsSection.append(row);
649
+ manualControls.push(one, three);
650
+ }
651
+ const clear = document.createElement('button');
652
+ clear.type = 'button';
653
+ clear.textContent = 'Clear';
654
+ clear.addEventListener('click', () => {
655
+ faults.clear();
656
+ });
657
+ faultsSection.append(clear);
658
+ manualControls.push(clear);
659
+ panel.append(faultsSection);
660
+
661
+ const reload = document.createElement('button');
662
+ reload.type = 'button';
663
+ reload.className = 'sandbox-test-reload';
664
+ reload.textContent = 'Reload game';
665
+ reload.addEventListener('click', async () => {
666
+ reload.disabled = true;
667
+ const result = await refresh();
668
+ reload.disabled = runActive;
669
+ if (result.ok) frame.src = frame.src;
670
+ else status.textContent = result.error?.message || 'Session refresh refused.';
671
+ });
672
+ manualControls.push(reload);
673
+ looksRight = document.createElement('button');
674
+ looksRight.type = 'button'; looksRight.textContent = 'Looks right'; looksRight.hidden = true;
675
+ looksWrong = document.createElement('button');
676
+ looksWrong.type = 'button'; looksWrong.textContent = 'Looks wrong'; looksWrong.hidden = true;
677
+ looksRight.addEventListener('click', () => finishReview('looks right'));
678
+ looksWrong.addEventListener('click', () => finishReview('looks wrong'));
679
+ panel.append(reload, looksRight, looksWrong, status);
680
+ return root;
681
+ }