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.
- package/README.md +20 -0
- package/engine-cli/connect.mjs +73 -0
- package/engine-cli/hbengine.mjs +565 -0
- package/engine-cli/package.json +1 -0
- package/engine-cli/templates.mjs +14 -0
- package/engine-conformance/analyse-worker.mjs +12 -0
- package/engine-conformance/analyse.mjs +4458 -0
- package/engine-conformance/bundle.mjs +208 -0
- package/engine-conformance/clock.mjs +294 -0
- package/engine-conformance/csp.go +56 -0
- package/engine-conformance/csp.json +6 -0
- package/engine-conformance/csp.mjs +20 -0
- package/engine-conformance/gzip-sizes.mjs +9 -0
- package/engine-conformance/package.json +1 -0
- package/engine-conformance/picture.mjs +55 -0
- package/engine-conformance/report.go +349 -0
- package/engine-conformance/run.mjs +1217 -0
- package/engine-conformance/scan.mjs +476 -0
- package/engine-conformance/testdata/clock-overlay/bundle/assets.json +1 -0
- package/engine-conformance/testdata/clock-overlay/bundle/game.css +17 -0
- package/engine-conformance/testdata/clock-overlay/bundle/game.js +83 -0
- package/engine-conformance/testdata/clock-overlay/bundle/hbtest.json +4 -0
- package/engine-conformance/testdata/clock-overlay/bundle/index.html +34 -0
- package/engine-conformance/testdata/clock-probe/bundle/assets.json +1 -0
- package/engine-conformance/testdata/clock-probe/bundle/game.css +17 -0
- package/engine-conformance/testdata/clock-probe/bundle/game.js +65 -0
- package/engine-conformance/testdata/clock-probe/bundle/hbtest.json +4 -0
- package/engine-conformance/testdata/clock-probe/bundle/index.html +35 -0
- package/engine-conformance/testdata/clock-probe/bundle/probe-worker.js +46 -0
- package/engine-conformance/testdata/clock-probe/bundle/probe.js +210 -0
- package/engine-conformance/testdata/clock-probe/math/dice.tsv +100 -0
- package/engine-conformance/testdata/clock-probe/math/manifest.json +2925 -0
- package/engine-conformance/testdata/clock-worker/bundle/assets.json +1 -0
- package/engine-conformance/testdata/clock-worker/bundle/clock-worker.js +14 -0
- package/engine-conformance/testdata/clock-worker/bundle/game.css +17 -0
- package/engine-conformance/testdata/clock-worker/bundle/game.js +61 -0
- package/engine-conformance/testdata/clock-worker/bundle/hbtest.json +4 -0
- package/engine-conformance/testdata/clock-worker/bundle/index.html +35 -0
- package/engine-conformance/testdata/clock-worker/math/dice.tsv +100 -0
- package/engine-conformance/testdata/clock-worker/math/manifest.json +2925 -0
- package/engine-conformance/travel.mjs +1209 -0
- package/engine-conformance/vendor/acorn/LICENSE +21 -0
- package/engine-conformance/vendor/acorn/acorn.mjs +6266 -0
- package/engine-conformance/wasm-releases.json +168 -0
- package/engine-examples/README.md +23 -0
- package/engine-examples/coin/AGENTS.md +3 -0
- package/engine-examples/coin/README.md +7 -0
- package/engine-examples/coin/bundle/assets.json +1 -0
- package/engine-examples/coin/bundle/game.css +5 -0
- package/engine-examples/coin/bundle/game.js +19 -0
- package/engine-examples/coin/bundle/hbtest.json +5 -0
- package/engine-examples/coin/bundle/index.html +16 -0
- package/engine-examples/coin/math/coin.tsv +2 -0
- package/engine-examples/coin/math/manifest.json +17 -0
- package/engine-examples/crash/AGENTS.md +3 -0
- package/engine-examples/crash/README.md +14 -0
- package/engine-examples/crash/bundle/assets.json +1 -0
- package/engine-examples/crash/bundle/game.css +8 -0
- package/engine-examples/crash/bundle/game.js +90 -0
- package/engine-examples/crash/bundle/hbtest.json +5 -0
- package/engine-examples/crash/bundle/index.html +20 -0
- package/engine-examples/crash/math/crash-01.tsv +2 -0
- package/engine-examples/crash/math/crash-02.tsv +2 -0
- package/engine-examples/crash/math/crash-03.tsv +2 -0
- package/engine-examples/crash/math/crash-04.tsv +2 -0
- package/engine-examples/crash/math/crash-05.tsv +2 -0
- package/engine-examples/crash/math/crash-06.tsv +2 -0
- package/engine-examples/crash/math/crash-07.tsv +2 -0
- package/engine-examples/crash/math/crash-08.tsv +2 -0
- package/engine-examples/crash/math/crash-09.tsv +2 -0
- package/engine-examples/crash/math/crash-10.tsv +2 -0
- package/engine-examples/crash/math/crash-11.tsv +2 -0
- package/engine-examples/crash/math/crash-12.tsv +2 -0
- package/engine-examples/crash/math/crash-13.tsv +2 -0
- package/engine-examples/crash/math/crash-14.tsv +2 -0
- package/engine-examples/crash/math/crash-15.tsv +2 -0
- package/engine-examples/crash/math/crash-16.tsv +2 -0
- package/engine-examples/crash/math/crash-17.tsv +2 -0
- package/engine-examples/crash/math/crash-18.tsv +2 -0
- package/engine-examples/crash/math/crash-19.tsv +2 -0
- package/engine-examples/crash/math/crash-20.tsv +2 -0
- package/engine-examples/crash/math/crash-21.tsv +2 -0
- package/engine-examples/crash/math/crash-22.tsv +2 -0
- package/engine-examples/crash/math/crash-23.tsv +2 -0
- package/engine-examples/crash/math/crash-24.tsv +2 -0
- package/engine-examples/crash/math/crash-25.tsv +2 -0
- package/engine-examples/crash/math/crash-26.tsv +2 -0
- package/engine-examples/crash/math/crash-27.tsv +2 -0
- package/engine-examples/crash/math/crash-28.tsv +2 -0
- package/engine-examples/crash/math/crash-29.tsv +2 -0
- package/engine-examples/crash/math/crash-30.tsv +2 -0
- package/engine-examples/crash/math/crash-31.tsv +2 -0
- package/engine-examples/crash/math/crash-32.tsv +2 -0
- package/engine-examples/crash/math/crash-33.tsv +2 -0
- package/engine-examples/crash/math/crash-34.tsv +2 -0
- package/engine-examples/crash/math/crash-35.tsv +2 -0
- package/engine-examples/crash/math/crash-36.tsv +2 -0
- package/engine-examples/crash/math/crash-37.tsv +2 -0
- package/engine-examples/crash/math/crash-38.tsv +2 -0
- package/engine-examples/crash/math/crash-39.tsv +2 -0
- package/engine-examples/crash/math/crash-40.tsv +2 -0
- package/engine-examples/crash/math/crash-41.tsv +2 -0
- package/engine-examples/crash/math/crash-42.tsv +2 -0
- package/engine-examples/crash/math/crash-43.tsv +2 -0
- package/engine-examples/crash/math/crash-44.tsv +2 -0
- package/engine-examples/crash/math/crash-45.tsv +2 -0
- package/engine-examples/crash/math/crash-46.tsv +2 -0
- package/engine-examples/crash/math/crash-47.tsv +2 -0
- package/engine-examples/crash/math/crash-48.tsv +2 -0
- package/engine-examples/crash/math/crash-49.tsv +2 -0
- package/engine-examples/crash/math/crash-50.tsv +2 -0
- package/engine-examples/crash/math/crash-51.tsv +2 -0
- package/engine-examples/crash/math/crash-52.tsv +2 -0
- package/engine-examples/crash/math/crash-53.tsv +2 -0
- package/engine-examples/crash/math/crash-54.tsv +2 -0
- package/engine-examples/crash/math/crash-55.tsv +2 -0
- package/engine-examples/crash/math/crash-56.tsv +2 -0
- package/engine-examples/crash/math/crash-57.tsv +2 -0
- package/engine-examples/crash/math/crash-58.tsv +2 -0
- package/engine-examples/crash/math/crash-59.tsv +2 -0
- package/engine-examples/crash/math/crash-60.tsv +2 -0
- package/engine-examples/crash/math/crash-61.tsv +2 -0
- package/engine-examples/crash/math/crash-62.tsv +2 -0
- package/engine-examples/crash/math/crash-63.tsv +2 -0
- package/engine-examples/crash/math/crash-64.tsv +2 -0
- package/engine-examples/crash/math/gen.mjs +31 -0
- package/engine-examples/crash/math/manifest.json +1 -0
- package/engine-examples/dice/AGENTS.md +3 -0
- package/engine-examples/dice/README.md +25 -0
- package/engine-examples/dice/bundle/assets.json +1 -0
- package/engine-examples/dice/bundle/game.css +17 -0
- package/engine-examples/dice/bundle/game.js +61 -0
- package/engine-examples/dice/bundle/hbtest.json +4 -0
- package/engine-examples/dice/bundle/index.html +34 -0
- package/engine-examples/dice/math/dice.tsv +100 -0
- package/engine-examples/dice/math/manifest.json +2925 -0
- package/engine-examples/keno/AGENTS.md +3 -0
- package/engine-examples/keno/README.md +11 -0
- package/engine-examples/keno/bundle/assets.json +1 -0
- package/engine-examples/keno/bundle/game.css +14 -0
- package/engine-examples/keno/bundle/game.js +86 -0
- package/engine-examples/keno/bundle/hbtest.json +4 -0
- package/engine-examples/keno/bundle/i18n.json +342 -0
- package/engine-examples/keno/bundle/index.html +20 -0
- package/engine-examples/keno/bundle/model.json +1 -0
- package/engine-examples/keno/math/gen.mjs +35 -0
- package/engine-examples/keno/math/keno-picks-1.tsv +2 -0
- package/engine-examples/keno/math/keno-picks-10.tsv +11 -0
- package/engine-examples/keno/math/keno-picks-2.tsv +3 -0
- package/engine-examples/keno/math/keno-picks-3.tsv +4 -0
- package/engine-examples/keno/math/keno-picks-4.tsv +5 -0
- package/engine-examples/keno/math/keno-picks-5.tsv +6 -0
- package/engine-examples/keno/math/keno-picks-6.tsv +7 -0
- package/engine-examples/keno/math/keno-picks-7.tsv +8 -0
- package/engine-examples/keno/math/keno-picks-8.tsv +9 -0
- package/engine-examples/keno/math/keno-picks-9.tsv +10 -0
- package/engine-examples/keno/math/manifest.json +1 -0
- package/engine-examples/keno/math-kit/pack.mjs +165 -0
- package/engine-examples/keno/math-kit/package.json +8 -0
- package/engine-examples/keno/math-kit/slot.mjs +94 -0
- package/engine-examples/keno/math-kit/tumble.mjs +94 -0
- package/engine-examples/lines/AGENTS.md +3 -0
- package/engine-examples/lines/README.md +20 -0
- package/engine-examples/lines/bundle/assets.json +1 -0
- package/engine-examples/lines/bundle/game.css +16 -0
- package/engine-examples/lines/bundle/game.js +125 -0
- package/engine-examples/lines/bundle/hbtest.json +4 -0
- package/engine-examples/lines/bundle/i18n.json +242 -0
- package/engine-examples/lines/bundle/index.html +23 -0
- package/engine-examples/lines/bundle/model.json +1 -0
- package/engine-examples/lines/math/gen.mjs +54 -0
- package/engine-examples/lines/math-kit/pack.mjs +165 -0
- package/engine-examples/lines/math-kit/package.json +8 -0
- package/engine-examples/lines/math-kit/slot.mjs +94 -0
- package/engine-examples/lines/math-kit/tumble.mjs +94 -0
- package/engine-examples/mines/AGENTS.md +3 -0
- package/engine-examples/mines/README.md +13 -0
- package/engine-examples/mines/bundle/assets.json +1 -0
- package/engine-examples/mines/bundle/game.css +12 -0
- package/engine-examples/mines/bundle/game.js +58 -0
- package/engine-examples/mines/bundle/hbtest.json +8 -0
- package/engine-examples/mines/bundle/index.html +16 -0
- package/engine-examples/mines/math/gen.mjs +40 -0
- package/engine-examples/mines/math/manifest.json +1 -0
- package/engine-examples/mines/math/mines-1-1.tsv +2 -0
- package/engine-examples/mines/math/mines-1-2.tsv +2 -0
- package/engine-examples/mines/math/mines-1-22.tsv +2 -0
- package/engine-examples/mines/math/mines-1-23.tsv +2 -0
- package/engine-examples/mines/math/mines-1-24.tsv +2 -0
- package/engine-examples/mines/math/mines-1-3.tsv +2 -0
- package/engine-examples/mines/math/mines-10-1.tsv +2 -0
- package/engine-examples/mines/math/mines-10-10.tsv +2 -0
- package/engine-examples/mines/math/mines-10-11.tsv +2 -0
- package/engine-examples/mines/math/mines-10-12.tsv +2 -0
- package/engine-examples/mines/math/mines-10-13.tsv +2 -0
- package/engine-examples/mines/math/mines-10-14.tsv +2 -0
- package/engine-examples/mines/math/mines-10-15.tsv +2 -0
- package/engine-examples/mines/math/mines-10-2.tsv +2 -0
- package/engine-examples/mines/math/mines-10-3.tsv +2 -0
- package/engine-examples/mines/math/mines-10-4.tsv +2 -0
- package/engine-examples/mines/math/mines-10-5.tsv +2 -0
- package/engine-examples/mines/math/mines-10-6.tsv +2 -0
- package/engine-examples/mines/math/mines-10-7.tsv +2 -0
- package/engine-examples/mines/math/mines-10-8.tsv +2 -0
- package/engine-examples/mines/math/mines-10-9.tsv +2 -0
- package/engine-examples/mines/math/mines-11-1.tsv +2 -0
- package/engine-examples/mines/math/mines-11-10.tsv +2 -0
- package/engine-examples/mines/math/mines-11-11.tsv +2 -0
- package/engine-examples/mines/math/mines-11-12.tsv +2 -0
- package/engine-examples/mines/math/mines-11-13.tsv +2 -0
- package/engine-examples/mines/math/mines-11-14.tsv +2 -0
- package/engine-examples/mines/math/mines-11-2.tsv +2 -0
- package/engine-examples/mines/math/mines-11-3.tsv +2 -0
- package/engine-examples/mines/math/mines-11-4.tsv +2 -0
- package/engine-examples/mines/math/mines-11-5.tsv +2 -0
- package/engine-examples/mines/math/mines-11-6.tsv +2 -0
- package/engine-examples/mines/math/mines-11-7.tsv +2 -0
- package/engine-examples/mines/math/mines-11-8.tsv +2 -0
- package/engine-examples/mines/math/mines-11-9.tsv +2 -0
- package/engine-examples/mines/math/mines-12-1.tsv +2 -0
- package/engine-examples/mines/math/mines-12-10.tsv +2 -0
- package/engine-examples/mines/math/mines-12-11.tsv +2 -0
- package/engine-examples/mines/math/mines-12-12.tsv +2 -0
- package/engine-examples/mines/math/mines-12-13.tsv +2 -0
- package/engine-examples/mines/math/mines-12-2.tsv +2 -0
- package/engine-examples/mines/math/mines-12-3.tsv +2 -0
- package/engine-examples/mines/math/mines-12-4.tsv +2 -0
- package/engine-examples/mines/math/mines-12-5.tsv +2 -0
- package/engine-examples/mines/math/mines-12-6.tsv +2 -0
- package/engine-examples/mines/math/mines-12-7.tsv +2 -0
- package/engine-examples/mines/math/mines-12-8.tsv +2 -0
- package/engine-examples/mines/math/mines-12-9.tsv +2 -0
- package/engine-examples/mines/math/mines-13-1.tsv +2 -0
- package/engine-examples/mines/math/mines-13-10.tsv +2 -0
- package/engine-examples/mines/math/mines-13-11.tsv +2 -0
- package/engine-examples/mines/math/mines-13-12.tsv +2 -0
- package/engine-examples/mines/math/mines-13-2.tsv +2 -0
- package/engine-examples/mines/math/mines-13-3.tsv +2 -0
- package/engine-examples/mines/math/mines-13-4.tsv +2 -0
- package/engine-examples/mines/math/mines-13-5.tsv +2 -0
- package/engine-examples/mines/math/mines-13-6.tsv +2 -0
- package/engine-examples/mines/math/mines-13-7.tsv +2 -0
- package/engine-examples/mines/math/mines-13-8.tsv +2 -0
- package/engine-examples/mines/math/mines-13-9.tsv +2 -0
- package/engine-examples/mines/math/mines-14-1.tsv +2 -0
- package/engine-examples/mines/math/mines-14-10.tsv +2 -0
- package/engine-examples/mines/math/mines-14-11.tsv +2 -0
- package/engine-examples/mines/math/mines-14-2.tsv +2 -0
- package/engine-examples/mines/math/mines-14-3.tsv +2 -0
- package/engine-examples/mines/math/mines-14-4.tsv +2 -0
- package/engine-examples/mines/math/mines-14-5.tsv +2 -0
- package/engine-examples/mines/math/mines-14-6.tsv +2 -0
- package/engine-examples/mines/math/mines-14-7.tsv +2 -0
- package/engine-examples/mines/math/mines-14-8.tsv +2 -0
- package/engine-examples/mines/math/mines-14-9.tsv +2 -0
- package/engine-examples/mines/math/mines-15-1.tsv +2 -0
- package/engine-examples/mines/math/mines-15-10.tsv +2 -0
- package/engine-examples/mines/math/mines-15-2.tsv +2 -0
- package/engine-examples/mines/math/mines-15-3.tsv +2 -0
- package/engine-examples/mines/math/mines-15-4.tsv +2 -0
- package/engine-examples/mines/math/mines-15-5.tsv +2 -0
- package/engine-examples/mines/math/mines-15-6.tsv +2 -0
- package/engine-examples/mines/math/mines-15-7.tsv +2 -0
- package/engine-examples/mines/math/mines-15-8.tsv +2 -0
- package/engine-examples/mines/math/mines-15-9.tsv +2 -0
- package/engine-examples/mines/math/mines-16-1.tsv +2 -0
- package/engine-examples/mines/math/mines-16-2.tsv +2 -0
- package/engine-examples/mines/math/mines-16-3.tsv +2 -0
- package/engine-examples/mines/math/mines-16-4.tsv +2 -0
- package/engine-examples/mines/math/mines-16-5.tsv +2 -0
- package/engine-examples/mines/math/mines-16-6.tsv +2 -0
- package/engine-examples/mines/math/mines-16-7.tsv +2 -0
- package/engine-examples/mines/math/mines-16-8.tsv +2 -0
- package/engine-examples/mines/math/mines-16-9.tsv +2 -0
- package/engine-examples/mines/math/mines-17-1.tsv +2 -0
- package/engine-examples/mines/math/mines-17-2.tsv +2 -0
- package/engine-examples/mines/math/mines-17-3.tsv +2 -0
- package/engine-examples/mines/math/mines-17-4.tsv +2 -0
- package/engine-examples/mines/math/mines-17-5.tsv +2 -0
- package/engine-examples/mines/math/mines-17-6.tsv +2 -0
- package/engine-examples/mines/math/mines-17-7.tsv +2 -0
- package/engine-examples/mines/math/mines-17-8.tsv +2 -0
- package/engine-examples/mines/math/mines-18-1.tsv +2 -0
- package/engine-examples/mines/math/mines-18-2.tsv +2 -0
- package/engine-examples/mines/math/mines-18-3.tsv +2 -0
- package/engine-examples/mines/math/mines-18-4.tsv +2 -0
- package/engine-examples/mines/math/mines-18-5.tsv +2 -0
- package/engine-examples/mines/math/mines-18-6.tsv +2 -0
- package/engine-examples/mines/math/mines-18-7.tsv +2 -0
- package/engine-examples/mines/math/mines-19-1.tsv +2 -0
- package/engine-examples/mines/math/mines-19-2.tsv +2 -0
- package/engine-examples/mines/math/mines-19-3.tsv +2 -0
- package/engine-examples/mines/math/mines-19-4.tsv +2 -0
- package/engine-examples/mines/math/mines-19-5.tsv +2 -0
- package/engine-examples/mines/math/mines-19-6.tsv +2 -0
- package/engine-examples/mines/math/mines-2-1.tsv +2 -0
- package/engine-examples/mines/math/mines-2-19.tsv +2 -0
- package/engine-examples/mines/math/mines-2-2.tsv +2 -0
- package/engine-examples/mines/math/mines-2-20.tsv +2 -0
- package/engine-examples/mines/math/mines-2-21.tsv +2 -0
- package/engine-examples/mines/math/mines-2-22.tsv +2 -0
- package/engine-examples/mines/math/mines-2-23.tsv +2 -0
- package/engine-examples/mines/math/mines-2-3.tsv +2 -0
- package/engine-examples/mines/math/mines-2-4.tsv +2 -0
- package/engine-examples/mines/math/mines-2-5.tsv +2 -0
- package/engine-examples/mines/math/mines-20-1.tsv +2 -0
- package/engine-examples/mines/math/mines-20-2.tsv +2 -0
- package/engine-examples/mines/math/mines-20-3.tsv +2 -0
- package/engine-examples/mines/math/mines-20-4.tsv +2 -0
- package/engine-examples/mines/math/mines-20-5.tsv +2 -0
- package/engine-examples/mines/math/mines-21-1.tsv +2 -0
- package/engine-examples/mines/math/mines-21-2.tsv +2 -0
- package/engine-examples/mines/math/mines-21-3.tsv +2 -0
- package/engine-examples/mines/math/mines-21-4.tsv +2 -0
- package/engine-examples/mines/math/mines-22-1.tsv +2 -0
- package/engine-examples/mines/math/mines-22-2.tsv +2 -0
- package/engine-examples/mines/math/mines-22-3.tsv +2 -0
- package/engine-examples/mines/math/mines-23-1.tsv +2 -0
- package/engine-examples/mines/math/mines-23-2.tsv +2 -0
- package/engine-examples/mines/math/mines-24-1.tsv +2 -0
- package/engine-examples/mines/math/mines-3-1.tsv +2 -0
- package/engine-examples/mines/math/mines-3-17.tsv +2 -0
- package/engine-examples/mines/math/mines-3-18.tsv +2 -0
- package/engine-examples/mines/math/mines-3-19.tsv +2 -0
- package/engine-examples/mines/math/mines-3-2.tsv +2 -0
- package/engine-examples/mines/math/mines-3-20.tsv +2 -0
- package/engine-examples/mines/math/mines-3-21.tsv +2 -0
- package/engine-examples/mines/math/mines-3-22.tsv +2 -0
- package/engine-examples/mines/math/mines-3-3.tsv +2 -0
- package/engine-examples/mines/math/mines-3-4.tsv +2 -0
- package/engine-examples/mines/math/mines-3-5.tsv +2 -0
- package/engine-examples/mines/math/mines-3-6.tsv +2 -0
- package/engine-examples/mines/math/mines-4-1.tsv +2 -0
- package/engine-examples/mines/math/mines-4-13.tsv +2 -0
- package/engine-examples/mines/math/mines-4-14.tsv +2 -0
- package/engine-examples/mines/math/mines-4-15.tsv +2 -0
- package/engine-examples/mines/math/mines-4-16.tsv +2 -0
- package/engine-examples/mines/math/mines-4-17.tsv +2 -0
- package/engine-examples/mines/math/mines-4-18.tsv +2 -0
- package/engine-examples/mines/math/mines-4-19.tsv +2 -0
- package/engine-examples/mines/math/mines-4-2.tsv +2 -0
- package/engine-examples/mines/math/mines-4-20.tsv +2 -0
- package/engine-examples/mines/math/mines-4-21.tsv +2 -0
- package/engine-examples/mines/math/mines-4-3.tsv +2 -0
- package/engine-examples/mines/math/mines-4-4.tsv +2 -0
- package/engine-examples/mines/math/mines-4-5.tsv +2 -0
- package/engine-examples/mines/math/mines-4-6.tsv +2 -0
- package/engine-examples/mines/math/mines-4-7.tsv +2 -0
- package/engine-examples/mines/math/mines-4-8.tsv +2 -0
- package/engine-examples/mines/math/mines-4-9.tsv +2 -0
- package/engine-examples/mines/math/mines-5-1.tsv +2 -0
- package/engine-examples/mines/math/mines-5-10.tsv +2 -0
- package/engine-examples/mines/math/mines-5-11.tsv +2 -0
- package/engine-examples/mines/math/mines-5-12.tsv +2 -0
- package/engine-examples/mines/math/mines-5-13.tsv +2 -0
- package/engine-examples/mines/math/mines-5-14.tsv +2 -0
- package/engine-examples/mines/math/mines-5-15.tsv +2 -0
- package/engine-examples/mines/math/mines-5-16.tsv +2 -0
- package/engine-examples/mines/math/mines-5-17.tsv +2 -0
- package/engine-examples/mines/math/mines-5-18.tsv +2 -0
- package/engine-examples/mines/math/mines-5-19.tsv +2 -0
- package/engine-examples/mines/math/mines-5-2.tsv +2 -0
- package/engine-examples/mines/math/mines-5-20.tsv +2 -0
- package/engine-examples/mines/math/mines-5-3.tsv +2 -0
- package/engine-examples/mines/math/mines-5-4.tsv +2 -0
- package/engine-examples/mines/math/mines-5-5.tsv +2 -0
- package/engine-examples/mines/math/mines-5-6.tsv +2 -0
- package/engine-examples/mines/math/mines-5-7.tsv +2 -0
- package/engine-examples/mines/math/mines-5-8.tsv +2 -0
- package/engine-examples/mines/math/mines-5-9.tsv +2 -0
- package/engine-examples/mines/math/mines-6-1.tsv +2 -0
- package/engine-examples/mines/math/mines-6-10.tsv +2 -0
- package/engine-examples/mines/math/mines-6-11.tsv +2 -0
- package/engine-examples/mines/math/mines-6-12.tsv +2 -0
- package/engine-examples/mines/math/mines-6-13.tsv +2 -0
- package/engine-examples/mines/math/mines-6-14.tsv +2 -0
- package/engine-examples/mines/math/mines-6-15.tsv +2 -0
- package/engine-examples/mines/math/mines-6-16.tsv +2 -0
- package/engine-examples/mines/math/mines-6-17.tsv +2 -0
- package/engine-examples/mines/math/mines-6-18.tsv +2 -0
- package/engine-examples/mines/math/mines-6-19.tsv +2 -0
- package/engine-examples/mines/math/mines-6-2.tsv +2 -0
- package/engine-examples/mines/math/mines-6-3.tsv +2 -0
- package/engine-examples/mines/math/mines-6-4.tsv +2 -0
- package/engine-examples/mines/math/mines-6-5.tsv +2 -0
- package/engine-examples/mines/math/mines-6-6.tsv +2 -0
- package/engine-examples/mines/math/mines-6-7.tsv +2 -0
- package/engine-examples/mines/math/mines-6-8.tsv +2 -0
- package/engine-examples/mines/math/mines-6-9.tsv +2 -0
- package/engine-examples/mines/math/mines-7-1.tsv +2 -0
- package/engine-examples/mines/math/mines-7-10.tsv +2 -0
- package/engine-examples/mines/math/mines-7-11.tsv +2 -0
- package/engine-examples/mines/math/mines-7-12.tsv +2 -0
- package/engine-examples/mines/math/mines-7-13.tsv +2 -0
- package/engine-examples/mines/math/mines-7-14.tsv +2 -0
- package/engine-examples/mines/math/mines-7-15.tsv +2 -0
- package/engine-examples/mines/math/mines-7-16.tsv +2 -0
- package/engine-examples/mines/math/mines-7-17.tsv +2 -0
- package/engine-examples/mines/math/mines-7-18.tsv +2 -0
- package/engine-examples/mines/math/mines-7-2.tsv +2 -0
- package/engine-examples/mines/math/mines-7-3.tsv +2 -0
- package/engine-examples/mines/math/mines-7-4.tsv +2 -0
- package/engine-examples/mines/math/mines-7-5.tsv +2 -0
- package/engine-examples/mines/math/mines-7-6.tsv +2 -0
- package/engine-examples/mines/math/mines-7-7.tsv +2 -0
- package/engine-examples/mines/math/mines-7-8.tsv +2 -0
- package/engine-examples/mines/math/mines-7-9.tsv +2 -0
- package/engine-examples/mines/math/mines-8-1.tsv +2 -0
- package/engine-examples/mines/math/mines-8-10.tsv +2 -0
- package/engine-examples/mines/math/mines-8-11.tsv +2 -0
- package/engine-examples/mines/math/mines-8-12.tsv +2 -0
- package/engine-examples/mines/math/mines-8-13.tsv +2 -0
- package/engine-examples/mines/math/mines-8-14.tsv +2 -0
- package/engine-examples/mines/math/mines-8-15.tsv +2 -0
- package/engine-examples/mines/math/mines-8-16.tsv +2 -0
- package/engine-examples/mines/math/mines-8-17.tsv +2 -0
- package/engine-examples/mines/math/mines-8-2.tsv +2 -0
- package/engine-examples/mines/math/mines-8-3.tsv +2 -0
- package/engine-examples/mines/math/mines-8-4.tsv +2 -0
- package/engine-examples/mines/math/mines-8-5.tsv +2 -0
- package/engine-examples/mines/math/mines-8-6.tsv +2 -0
- package/engine-examples/mines/math/mines-8-7.tsv +2 -0
- package/engine-examples/mines/math/mines-8-8.tsv +2 -0
- package/engine-examples/mines/math/mines-8-9.tsv +2 -0
- package/engine-examples/mines/math/mines-9-1.tsv +2 -0
- package/engine-examples/mines/math/mines-9-10.tsv +2 -0
- package/engine-examples/mines/math/mines-9-11.tsv +2 -0
- package/engine-examples/mines/math/mines-9-12.tsv +2 -0
- package/engine-examples/mines/math/mines-9-13.tsv +2 -0
- package/engine-examples/mines/math/mines-9-14.tsv +2 -0
- package/engine-examples/mines/math/mines-9-15.tsv +2 -0
- package/engine-examples/mines/math/mines-9-16.tsv +2 -0
- package/engine-examples/mines/math/mines-9-2.tsv +2 -0
- package/engine-examples/mines/math/mines-9-3.tsv +2 -0
- package/engine-examples/mines/math/mines-9-4.tsv +2 -0
- package/engine-examples/mines/math/mines-9-5.tsv +2 -0
- package/engine-examples/mines/math/mines-9-6.tsv +2 -0
- package/engine-examples/mines/math/mines-9-7.tsv +2 -0
- package/engine-examples/mines/math/mines-9-8.tsv +2 -0
- package/engine-examples/mines/math/mines-9-9.tsv +2 -0
- package/engine-examples/plinko/AGENTS.md +3 -0
- package/engine-examples/plinko/README.md +9 -0
- package/engine-examples/plinko/bundle/assets.json +1 -0
- package/engine-examples/plinko/bundle/game.css +5 -0
- package/engine-examples/plinko/bundle/game.js +92 -0
- package/engine-examples/plinko/bundle/hbtest.json +4 -0
- package/engine-examples/plinko/bundle/index.html +16 -0
- package/engine-examples/plinko/bundle/model.json +1 -0
- package/engine-examples/plinko/math/gen.mjs +40 -0
- package/engine-examples/plinko/math/manifest.json +1 -0
- package/engine-examples/plinko/math/plinko-12-high.tsv +13 -0
- package/engine-examples/plinko/math/plinko-12-low.tsv +13 -0
- package/engine-examples/plinko/math/plinko-12-medium.tsv +13 -0
- package/engine-examples/plinko/math/plinko-16-high.tsv +17 -0
- package/engine-examples/plinko/math/plinko-16-low.tsv +17 -0
- package/engine-examples/plinko/math/plinko-16-medium.tsv +17 -0
- package/engine-examples/plinko/math/plinko-8-high.tsv +9 -0
- package/engine-examples/plinko/math/plinko-8-low.tsv +9 -0
- package/engine-examples/plinko/math/plinko-8-medium.tsv +9 -0
- package/engine-examples/plinko/math-kit/pack.mjs +165 -0
- package/engine-examples/plinko/math-kit/package.json +8 -0
- package/engine-examples/plinko/math-kit/slot.mjs +94 -0
- package/engine-examples/plinko/math-kit/tumble.mjs +94 -0
- package/engine-examples/slot/AGENTS.md +3 -0
- package/engine-examples/slot/README.md +26 -0
- package/engine-examples/slot/bundle/assets.json +1 -0
- package/engine-examples/slot/bundle/game.css +19 -0
- package/engine-examples/slot/bundle/game.js +42 -0
- package/engine-examples/slot/bundle/hbtest.json +4 -0
- package/engine-examples/slot/bundle/index.html +23 -0
- package/engine-examples/slot/math/gen.mjs +79 -0
- package/engine-examples/slot/math/manifest.json +1 -0
- package/engine-examples/slot/math/slot.tsv +110592 -0
- package/engine-examples/tower/AGENTS.md +3 -0
- package/engine-examples/tower/README.md +21 -0
- package/engine-examples/tower/bundle/assets.json +1 -0
- package/engine-examples/tower/bundle/game.css +8 -0
- package/engine-examples/tower/bundle/game.js +37 -0
- package/engine-examples/tower/bundle/hbtest.json +5 -0
- package/engine-examples/tower/bundle/index.html +16 -0
- package/engine-examples/tower/math/floor.tsv +4 -0
- package/engine-examples/tower/math/manifest.json +61 -0
- package/engine-examples/tumble/AGENTS.md +3 -0
- package/engine-examples/tumble/README.md +18 -0
- package/engine-examples/tumble/bundle/assets.json +1 -0
- package/engine-examples/tumble/bundle/game.css +13 -0
- package/engine-examples/tumble/bundle/game.js +119 -0
- package/engine-examples/tumble/bundle/hbtest.json +4 -0
- package/engine-examples/tumble/bundle/i18n.json +122 -0
- package/engine-examples/tumble/bundle/index.html +20 -0
- package/engine-examples/tumble/bundle/model.json +1 -0
- package/engine-examples/tumble/math/gen.mjs +38 -0
- package/engine-examples/tumble/math-kit/pack.mjs +165 -0
- package/engine-examples/tumble/math-kit/package.json +8 -0
- package/engine-examples/tumble/math-kit/slot.mjs +94 -0
- package/engine-examples/tumble/math-kit/tumble.mjs +94 -0
- package/engine-examples/wheel/README.md +14 -0
- package/engine-examples/wheel/bundle/assets.json +1 -0
- package/engine-examples/wheel/bundle/game.css +7 -0
- package/engine-examples/wheel/bundle/game.js +51 -0
- package/engine-examples/wheel/bundle/hbtest.json +4 -0
- package/engine-examples/wheel/bundle/index.html +19 -0
- package/engine-examples/wheel/math/high.tsv +24 -0
- package/engine-examples/wheel/math/low.tsv +24 -0
- package/engine-examples/wheel/math/manifest.json +48 -0
- package/engine-examples/wheel/math/medium.tsv +24 -0
- package/engine-math/pack.mjs +165 -0
- package/engine-math/package.json +1 -0
- package/engine-math/slot.mjs +94 -0
- package/engine-math/tumble.mjs +94 -0
- package/engine-mcp/package.json +1 -0
- package/engine-mcp/server.mjs +151 -0
- package/engine-sdk/GUIDE.md +855 -0
- package/engine-sdk/assets.js +92 -0
- package/engine-sdk/autoplay.js +82 -0
- package/engine-sdk/bet-field.js +391 -0
- package/engine-sdk/kit/fiat-coins.js +51 -0
- package/engine-sdk/kit/numeric-input.js +116 -0
- package/engine-sdk/kit/wallet-format.js +423 -0
- package/engine-sdk/marks.js +22 -0
- package/engine-sdk/messages.js +289 -0
- package/engine-sdk/money.js +89 -0
- package/engine-sdk/package.json +1 -0
- package/engine-sdk/protocol.js +263 -0
- package/engine-sdk/sdk.d.ts +138 -0
- package/engine-sdk/sdk.js +330 -0
- package/engine-sdk/stake.js +68 -0
- package/engine-sdk/ui.d.ts +36 -0
- package/engine-sdk/ui.js +340 -0
- package/engine-shell/core/faults.js +127 -0
- package/engine-shell/core/rgs.js +193 -0
- package/engine-shell/core/router.js +917 -0
- package/engine-shell/host/boot.js +408 -0
- package/engine-shell/host/engine-v1.js +438 -0
- package/engine-shell/host/fair.js +285 -0
- package/engine-shell/host/fonts/SOURCE.json +11 -0
- package/engine-shell/host/fonts/figtree-400.woff2 +0 -0
- package/engine-shell/host/fonts/figtree-500.woff2 +0 -0
- package/engine-shell/host/fonts/figtree-600.woff2 +0 -0
- package/engine-shell/host/fonts/red-hat-display-700.woff2 +0 -0
- package/engine-shell/host/index.html +230 -0
- package/engine-shell/host/page.js +809 -0
- package/engine-shell/host/sandbox-panel.js +681 -0
- package/engine-shell/host/sandbox-scenarios.js +554 -0
- package/engine-shell/host/site-bridge.js +87 -0
- package/engine-shell/host/strings.js +916 -0
- package/engine-shell/package.json +1 -0
- package/engine-shell/verifiers/badcat/coin.tsv +2 -0
- package/engine-shell/verifiers/badcat/dice-2.tsv +11 -0
- package/engine-shell/verifiers/badcat/dice-5.tsv +26 -0
- package/engine-shell/verifiers/badcat/dice-9.tsv +46 -0
- package/engine-shell/verifiers/badcat/manifest.json +3344 -0
- package/engine-shell/verifiers/badcat/rps.tsv +3 -0
- package/engine-shell/verifiers/badcat/triple.tsv +66 -0
- package/engine-shell/verifiers/engine-verify.js +358 -0
- package/engine-shell/verifiers/package.json +5 -0
- package/engine-shell/verifiers/parity/manifest.json +27 -0
- package/engine-shell/verifiers/parity/wide.tsv +5 -0
- package/engine-shell/verifiers/parity-vectors.json +244 -0
- package/engine-shell/verifiers/vectors.json +100 -0
- 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
|
+
}
|