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,554 @@
|
|
|
1
|
+
/** Guided sandbox checks. The game still supplies every click. */
|
|
2
|
+
|
|
3
|
+
const SCENARIO_TIMEOUT_MS = 90_000;
|
|
4
|
+
export const SCENARIO_TIMEOUT = Symbol('scenario-timeout');
|
|
5
|
+
|
|
6
|
+
function parseLimitValue(value, fallback = 0) {
|
|
7
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
8
|
+
if (typeof value === 'string' && /^\d+(\.\d+)?$/.test(value)) {
|
|
9
|
+
const parsed = Number(value);
|
|
10
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
11
|
+
}
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function minimumStake(ctx) {
|
|
16
|
+
const configured = parseLimitValue(ctx.limits?.min);
|
|
17
|
+
if (configured > 0) return configured;
|
|
18
|
+
const decimals = Number.isInteger(ctx.walletDecimals) ? ctx.walletDecimals : 8;
|
|
19
|
+
return 10 ** -Math.min(8, Math.max(0, decimals));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function walletUnit(ctx) {
|
|
23
|
+
const decimals = Number.isInteger(ctx.walletDecimals) ? ctx.walletDecimals : 8;
|
|
24
|
+
return 10 ** -Math.min(8, Math.max(0, decimals));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function entries(ctx) {
|
|
28
|
+
return ctx.entries || [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function stakeLimit(ctx, entry) {
|
|
32
|
+
const maxMap = ctx.limits?.maxStake || {};
|
|
33
|
+
const mode = maxMap[entry.mode];
|
|
34
|
+
const max = mode && mode[entry.bet] !== undefined ? parseLimitValue(mode[entry.bet]) : parseLimitValue(ctx.limits?.max);
|
|
35
|
+
return { min: minimumStake(ctx), max: max > 0 ? max : null };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function playEntry(ctx, predicate = () => true) {
|
|
39
|
+
return entries(ctx).find((entry) => entry?.mode && entry?.bet && predicate(entry));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function tableEntry(ctx, predicate = () => true) {
|
|
43
|
+
return playEntry(ctx, (entry) => entry.indices?.length === 1 && !entry.ladder && predicate(entry));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ladderEntry(ctx, suffix) {
|
|
47
|
+
return entries(ctx).find((entry) => entry?.indices?.length > 1 && (!suffix || String(entry.label).includes(suffix)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function topEntry(ctx) {
|
|
51
|
+
return entries(ctx)
|
|
52
|
+
.filter((entry) => entry?.mode && entry?.bet && !entry.loss && (entry.multiplier || 0) > 0)
|
|
53
|
+
.sort((a, b) => (b.multiplier || 0) - (a.multiplier || 0))[0] || playEntry(ctx);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function losingEntry(ctx, subject) {
|
|
57
|
+
const sameSubject = (entry) => !subject || (entry.mode === subject.mode && entry.bet === subject.bet);
|
|
58
|
+
return tableEntry(ctx, (entry) => sameSubject(entry) && (entry.loss === true || (entry.multiplier || 0) === 0 || /lose|loss/i.test(entry.label || '')))
|
|
59
|
+
|| entries(ctx).find((entry) => sameSubject(entry) && entry.indices?.length > 1 && /Bust at step 1/i.test(entry.label || ''));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function stakeLimits(ctx) {
|
|
63
|
+
const out = {};
|
|
64
|
+
for (const entry of entries(ctx)) {
|
|
65
|
+
if (!entry?.mode || !entry?.bet) continue;
|
|
66
|
+
out[entry.mode] ||= {};
|
|
67
|
+
out[entry.mode][entry.bet] = stakeLimit(ctx, entry);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function result(status, detail, observation, extra = {}) {
|
|
73
|
+
return {
|
|
74
|
+
status,
|
|
75
|
+
detail,
|
|
76
|
+
warnings: extra.warnings || [],
|
|
77
|
+
...(observation ? { observation } : {}),
|
|
78
|
+
...extra,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function expected(detail, observation, extra = {}) {
|
|
83
|
+
const status = extra.review ? 'needs-verdict' : 'expected';
|
|
84
|
+
return result(status, detail, observation, extra);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function review(detail, observation) {
|
|
88
|
+
return result('needs-verdict', detail, observation, { review: true });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function refusalFor(observation, request = 'play') {
|
|
92
|
+
return observation?.type === 'refused' && (observation.request?.type === request || observation.request === request);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function describeBet(round) {
|
|
96
|
+
return round?.bet || round?.mode || 'another bet';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function betInstruction(ctx, entry, prefix = 'Bet') {
|
|
100
|
+
const mode = entry.modeLabel || entry.mode;
|
|
101
|
+
const bet = entry.betLabel || entry.bet;
|
|
102
|
+
return `${prefix} ${minimumStake(ctx)} on ${mode} / ${bet}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function checkRound(observation, entry, check = {}) {
|
|
106
|
+
const round = observation?.round;
|
|
107
|
+
if (!round) return result('different', 'Different: the router did not include the round.', observation);
|
|
108
|
+
if (round.mode !== entry.mode || round.bet !== entry.bet) return result('different', `Different: bet on ${describeBet(round)}, expected ${entry.bet}.`, observation);
|
|
109
|
+
if (round.forced !== true) return result('different', 'Different: the round was not forced.', observation);
|
|
110
|
+
if (check.settledState && round.state !== 'settled') return result('different', 'Different: the round was not settled.', observation);
|
|
111
|
+
if (check.settled && round.step !== entry.indices.length) return result('different', 'Different: the ladder did not settle at its final step.', observation);
|
|
112
|
+
if (check.multBp !== undefined && round.multBp !== check.multBp) return result('different', `Different: multiplier ${round.multBp}, expected ${check.multBp}.`, observation);
|
|
113
|
+
if (check.loss && round.multBp !== 0) return result('different', 'Different: the round was not a loss.', observation);
|
|
114
|
+
if (check.winAfterLoss && round.multBp <= 0) return result('different', 'Different: the second round was not a win.', observation);
|
|
115
|
+
if (check.survived && !(round.busted === false && round.survived === entry.indices.length)) return result('different', 'Different: the ladder did not survive every step.', observation);
|
|
116
|
+
if (check.busted && !(round.busted === true && round.survived === 0 && round.step === 1)) return result('different', 'Different: the ladder did not bust at step 1.', observation);
|
|
117
|
+
return expected(check.detail || 'The runner observed the expected round.', observation, check.review ? { review: true } : {});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function wait(ctx, predicate, instruction) {
|
|
121
|
+
try {
|
|
122
|
+
ctx.beforeWait?.();
|
|
123
|
+
const observation = await ctx.waitFor(predicate, SCENARIO_TIMEOUT_MS);
|
|
124
|
+
if (observation === SCENARIO_TIMEOUT) return result('skipped', `Skipped after 90 seconds: ${instruction}`);
|
|
125
|
+
if (observation) return { observation };
|
|
126
|
+
const status = ctx.stopRequested?.() ? 'stopped' : ctx.skipRequested?.() ? 'skipped' : 'needs-verdict';
|
|
127
|
+
if (status === 'needs-verdict') return review(`The runner saw no observation. Did the game show the expected result?`);
|
|
128
|
+
const verb = status === 'stopped' ? 'Stopped' : 'Skipped';
|
|
129
|
+
return result(status, `${verb}: ${instruction}`);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error?.code === 'timeout' || error?.name === 'TimeoutError') return result('skipped', `Skipped after 90 seconds: ${instruction}`);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function setupBalance(ctx, amount) {
|
|
137
|
+
const response = await ctx.setBalance(amount);
|
|
138
|
+
return response?.ok === false ? result('different', `Different: setup refused (${response.error?.message || 'balance refused'}).`) : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function setupForce(ctx, entry) {
|
|
142
|
+
if (!entry) return result('different', 'Different: no forceable bet is available.');
|
|
143
|
+
const response = await ctx.force(entry.indices, entry.mode);
|
|
144
|
+
return response?.ok === false ? result('different', `Different: setup refused (${response.error?.message || 'force refused'}).`) : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function ladderInstruction(ctx, entry, suffix) {
|
|
148
|
+
if (entry.autoAdvance) return betInstruction(ctx, entry);
|
|
149
|
+
return `${betInstruction(ctx, entry)}, ${suffix}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const definitions = [
|
|
153
|
+
{
|
|
154
|
+
id: 'cant-afford-minimum', group: 'Balance', title: "Can't afford the minimum stake", todo: 'Press Bet once', look: 'An insufficient-balance refusal', applies: (ctx) => !!playEntry(ctx),
|
|
155
|
+
async run(ctx) {
|
|
156
|
+
const setup = await setupBalance(ctx, Math.max(0, minimumStake(ctx) - walletUnit(ctx)));
|
|
157
|
+
if (setup) return setup;
|
|
158
|
+
ctx.say?.(betInstruction(ctx, playEntry(ctx)));
|
|
159
|
+
const waited = await wait(ctx, (event) => refusalFor(event) || event.type === 'settled', this.todo);
|
|
160
|
+
if (!waited.observation) return waited;
|
|
161
|
+
if (waited.observation.type === 'settled') return result('different', 'Different: a settled round occurred.', waited.observation);
|
|
162
|
+
return waited.observation.code === 'insufficient-balance'
|
|
163
|
+
? expected('Refused with insufficient-balance', waited.observation, { review: true })
|
|
164
|
+
: result('different', `Different: refused with ${waited.observation.code}.`, waited.observation);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'exactly-one-bet', group: 'Balance', title: 'Exactly one bet', todo: 'Bet the minimum stake', look: 'One losing round, then insufficient-balance', applies: (ctx) => !!playEntry(ctx, (entry) => !!losingEntry(ctx, entry)),
|
|
169
|
+
async run(ctx) {
|
|
170
|
+
const entry = playEntry(ctx, (candidate) => !!losingEntry(ctx, candidate));
|
|
171
|
+
let setup = await setupBalance(ctx, minimumStake(ctx));
|
|
172
|
+
if (setup) return setup;
|
|
173
|
+
setup = await setupForce(ctx, losingEntry(ctx, entry));
|
|
174
|
+
if (setup) return setup;
|
|
175
|
+
const suffix = entry.indices.length > 1 ? ', then take the first step' : '';
|
|
176
|
+
ctx.say?.(`Bet the minimum stake (${minimumStake(ctx)}) on ${entry.modeLabel || entry.mode} / ${entry.betLabel || entry.bet}${suffix}`);
|
|
177
|
+
const first = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
178
|
+
if (!first.observation) return first;
|
|
179
|
+
const firstCheck = checkRound(first.observation, losingEntry(ctx, entry), { loss: true });
|
|
180
|
+
if (firstCheck.status !== 'expected') return firstCheck;
|
|
181
|
+
const second = await wait(ctx, (event) => refusalFor(event), this.todo);
|
|
182
|
+
if (!second.observation) return second;
|
|
183
|
+
return second.observation.code === 'insufficient-balance'
|
|
184
|
+
? expected('Exactly one losing bet, then insufficient-balance', second.observation, { review: true })
|
|
185
|
+
: result('different', `Different: second bet refused with ${second.observation.code}.`, second.observation);
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'stake-above-balance', group: 'Balance', title: 'Stake above the balance', todo: 'Press Bet with a stake above the balance', look: 'An insufficient-balance refusal or the game explanation', applies: (ctx) => !!playEntry(ctx),
|
|
190
|
+
async run(ctx) {
|
|
191
|
+
const entry = playEntry(ctx);
|
|
192
|
+
const setup = await setupBalance(ctx, minimumStake(ctx));
|
|
193
|
+
if (setup) return setup;
|
|
194
|
+
ctx.say?.(`Bet more than the balance (${minimumStake(ctx)}) on ${entry.modeLabel || entry.mode} / ${entry.betLabel || entry.bet}`);
|
|
195
|
+
const waited = await wait(ctx, (event) => refusalFor(event) || event.type === 'settled', this.todo);
|
|
196
|
+
if (!waited.observation) return waited;
|
|
197
|
+
if (waited.observation.type === 'settled') return result('different', 'Different: a settled round occurred.', waited.observation);
|
|
198
|
+
return waited.observation.code === 'insufficient-balance'
|
|
199
|
+
? expected('Refused with insufficient-balance', waited.observation, { review: true })
|
|
200
|
+
: result('different', `Different: refused with ${waited.observation.code}.`, waited.observation);
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: 'empty-wallet', group: 'Balance', title: 'Empty wallet', todo: 'Press Bet once', look: 'A refusal for insufficient balance', applies: (ctx) => !!playEntry(ctx),
|
|
205
|
+
async run(ctx) {
|
|
206
|
+
const setup = await setupBalance(ctx, 0);
|
|
207
|
+
if (setup) return setup;
|
|
208
|
+
ctx.say?.(`Empty wallet — ${betInstruction(ctx, playEntry(ctx))}`);
|
|
209
|
+
const waited = await wait(ctx, (event) => refusalFor(event) || event.type === 'settled', this.todo);
|
|
210
|
+
if (!waited.observation) return waited;
|
|
211
|
+
if (waited.observation.type === 'settled') return result('different', 'Different: a settled round occurred.', waited.observation);
|
|
212
|
+
return waited.observation.code === 'insufficient-balance'
|
|
213
|
+
? expected('Refused with insufficient-balance', waited.observation, { review: true })
|
|
214
|
+
: result('different', `Different: refused with ${waited.observation.code}.`, waited.observation);
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: 'biggest-win', group: 'Outcomes', title: 'Biggest win', todo: 'Bet once', look: 'A forced round with the top multiplier', applies: (ctx) => !!tableEntry(ctx),
|
|
219
|
+
async run(ctx) {
|
|
220
|
+
const entry = topEntry(ctx);
|
|
221
|
+
const setup = await setupForce(ctx, entry);
|
|
222
|
+
if (setup) return setup;
|
|
223
|
+
ctx.say?.(betInstruction(ctx, entry));
|
|
224
|
+
const waited = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
225
|
+
if (!waited.observation) return waited;
|
|
226
|
+
return checkRound(waited.observation, entry, { multBp: entry.multiplier, detail: `Settled at ${entry.multiplier / 10000}×.`, review: true });
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: 'losing-streak', group: 'Outcomes', title: 'Losing streak', todo: 'Bet five times', look: 'Five forced losses', applies: (ctx) => !!losingEntry(ctx),
|
|
231
|
+
async run(ctx) {
|
|
232
|
+
const entry = losingEntry(ctx);
|
|
233
|
+
for (let index = 0; index < 5; index += 1) {
|
|
234
|
+
const setup = await setupForce(ctx, entry);
|
|
235
|
+
if (setup) return setup;
|
|
236
|
+
ctx.say?.(`Round ${index + 1}/5 ready — ${betInstruction(ctx, entry)}`);
|
|
237
|
+
const waited = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
238
|
+
if (!waited.observation) return waited;
|
|
239
|
+
const checked = checkRound(waited.observation, entry, { loss: true, detail: `Round ${index + 1}/5 was a loss.` });
|
|
240
|
+
if (checked.status !== 'expected') return result('different', `Different: round ${index + 1} was not a loss.`, waited.observation);
|
|
241
|
+
}
|
|
242
|
+
return expected('Five forced losses.', undefined, { review: true });
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: 'win-after-loss', group: 'Outcomes', title: 'Win after a loss', todo: 'Bet twice', look: 'A loss followed by a forced win', applies: (ctx) => !!losingEntry(ctx) && !!tableEntry(ctx, (entry) => !entry.loss && (entry.multiplier || 0) > 0),
|
|
247
|
+
async run(ctx) {
|
|
248
|
+
const lossEntry = losingEntry(ctx);
|
|
249
|
+
const winEntry = topEntry(ctx);
|
|
250
|
+
let setup = await setupForce(ctx, lossEntry);
|
|
251
|
+
if (setup) return setup;
|
|
252
|
+
ctx.say?.(`${betInstruction(ctx, lossEntry)} for a loss, then ${betInstruction(ctx, winEntry)}`);
|
|
253
|
+
const loss = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
254
|
+
if (!loss.observation) return loss;
|
|
255
|
+
const lossCheck = checkRound(loss.observation, lossEntry, { loss: true });
|
|
256
|
+
if (lossCheck.status !== 'expected') return lossCheck;
|
|
257
|
+
setup = await setupForce(ctx, winEntry);
|
|
258
|
+
if (setup) return setup;
|
|
259
|
+
const win = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
260
|
+
if (!win.observation) return win;
|
|
261
|
+
return checkRound(win.observation, winEntry, { winAfterLoss: true, detail: 'A win followed the loss.', review: true });
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
id: 'survive-every-step', group: 'Outcomes', title: 'Survive every step', todo: 'Bet and advance through every step', look: 'The ladder survives and settles', applies: (ctx) => !!ladderEntry(ctx, 'Survive every step'),
|
|
266
|
+
async run(ctx) {
|
|
267
|
+
const entry = ladderEntry(ctx, 'Survive every step');
|
|
268
|
+
const setup = await setupForce(ctx, entry);
|
|
269
|
+
if (setup) return setup;
|
|
270
|
+
ctx.say?.(ladderInstruction(ctx, entry, 'then advance through every step'));
|
|
271
|
+
const waited = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
272
|
+
if (!waited.observation) return waited;
|
|
273
|
+
return checkRound(waited.observation, entry, {
|
|
274
|
+
survived: true,
|
|
275
|
+
settledState: true,
|
|
276
|
+
settled: entry.autoAdvance,
|
|
277
|
+
detail: 'The ladder survived every step.',
|
|
278
|
+
review: true,
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 'bust-at-step-one', group: 'Outcomes', title: 'Bust at step 1', todo: 'Bet and take the first step', look: 'The ladder busts at step 1', applies: (ctx) => !!ladderEntry(ctx, 'Bust at step 1'),
|
|
284
|
+
async run(ctx) {
|
|
285
|
+
const entry = ladderEntry(ctx, 'Bust at step 1');
|
|
286
|
+
const setup = await setupForce(ctx, entry);
|
|
287
|
+
if (setup) return setup;
|
|
288
|
+
ctx.say?.(ladderInstruction(ctx, entry, 'then take the first step'));
|
|
289
|
+
const waited = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
290
|
+
if (!waited.observation) return waited;
|
|
291
|
+
return checkRound(waited.observation, entry, {
|
|
292
|
+
busted: true,
|
|
293
|
+
settledState: true,
|
|
294
|
+
settled: entry.autoAdvance,
|
|
295
|
+
detail: 'The ladder busted at step 1.',
|
|
296
|
+
review: true,
|
|
297
|
+
});
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
id: 'server-hiccup', group: 'Failures', title: 'Server hiccup that recovers', todo: 'Bet once', look: 'The round settles after a retry', applies: (ctx) => !!playEntry(ctx),
|
|
302
|
+
async run(ctx) {
|
|
303
|
+
ctx.armFault?.('server', { times: 1, only: 'play' });
|
|
304
|
+
ctx.say?.(betInstruction(ctx, playEntry(ctx)));
|
|
305
|
+
const waited = await wait(ctx, (event) => event.type === 'settled' || refusalFor(event), this.todo);
|
|
306
|
+
if (!waited.observation) return waited;
|
|
307
|
+
if (waited.observation.type === 'settled') return expected('Settled after the server retry.', waited.observation, { review: true });
|
|
308
|
+
return result('different', `Different: refused ${waited.observation.code}.`, waited.observation);
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: 'outage', group: 'Failures', title: 'Outage', todo: 'Bet during the outage, then once more after it clears', look: 'Unavailable refusal, then a normal settled bet', applies: (ctx) => !!playEntry(ctx),
|
|
313
|
+
async run(ctx) {
|
|
314
|
+
ctx.armFault?.('unavailable', { times: 3, only: 'play' });
|
|
315
|
+
ctx.say?.(`${betInstruction(ctx, playEntry(ctx))} during the outage`);
|
|
316
|
+
const refused = await wait(ctx, (event) => refusalFor(event), this.todo);
|
|
317
|
+
if (!refused.observation) return refused;
|
|
318
|
+
if (refused.observation.code !== 'unavailable') return result('different', `Different: outage refused with ${refused.observation.code}.`, refused.observation);
|
|
319
|
+
ctx.clearFaults?.();
|
|
320
|
+
ctx.say?.(`The outage is clear — ${betInstruction(ctx, playEntry(ctx))}`);
|
|
321
|
+
const settled = await wait(ctx, (event) => event.type === 'settled', this.todo);
|
|
322
|
+
if (!settled.observation) return settled;
|
|
323
|
+
return expected('Unavailable, then a normal settled bet.', settled.observation, { review: true });
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: 'slow-network', group: 'Failures', title: 'Slow network', todo: 'Bet once', look: 'The round settles after the delay', applies: (ctx) => !!playEntry(ctx),
|
|
328
|
+
async run(ctx) {
|
|
329
|
+
ctx.armFault?.('slow', { times: 1, ms: 3000, only: 'play' });
|
|
330
|
+
ctx.say?.(betInstruction(ctx, playEntry(ctx)));
|
|
331
|
+
const waited = await wait(ctx, (event) => event.type === 'settled' || refusalFor(event), this.todo);
|
|
332
|
+
if (!waited.observation) return waited;
|
|
333
|
+
if (waited.observation.type === 'settled') return expected('Settled after the slow response.', waited.observation, { review: true });
|
|
334
|
+
return result('different', `Different: refused ${waited.observation.code}.`, waited.observation);
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: 'session-expires', group: 'Failures', title: 'Session expires', todo: 'Bet once', look: 'The session ends', applies: (ctx) => !!playEntry(ctx),
|
|
339
|
+
async run(ctx) {
|
|
340
|
+
ctx.armFault?.('session', { times: 1, only: 'play' });
|
|
341
|
+
ctx.say?.(`${betInstruction(ctx, playEntry(ctx))}; this is the final scenario`);
|
|
342
|
+
const waited = await wait(ctx, (event) => event.type === 'ended' || refusalFor(event), this.todo);
|
|
343
|
+
if (!waited.observation) return waited;
|
|
344
|
+
return waited.observation.type === 'ended' || waited.observation.code === 'session-ended'
|
|
345
|
+
? expected('The session ended.', waited.observation, { review: true })
|
|
346
|
+
: result('different', `Different: refused with ${waited.observation.code}.`, waited.observation);
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: 'resume-interrupted-round', group: 'Recovery', title: 'Resume an interrupted round', todo: 'Bet, then wait for the reload', look: 'The open round is shown after reload', applies: (ctx) => {
|
|
351
|
+
const entry = ladderEntry(ctx, 'Survive every step');
|
|
352
|
+
return !!entry && !entry.autoAdvance && typeof ctx.reload === 'function';
|
|
353
|
+
},
|
|
354
|
+
async run(ctx) {
|
|
355
|
+
const entry = ladderEntry(ctx, 'Survive every step');
|
|
356
|
+
const setup = await setupForce(ctx, entry);
|
|
357
|
+
if (setup) return setup;
|
|
358
|
+
ctx.say?.(`${betInstruction(ctx, entry)} to open the ladder; wait for reload`);
|
|
359
|
+
const opened = await wait(ctx, (event) => event.type === 'opened' && event.round?.state === 'open', this.todo);
|
|
360
|
+
if (!opened.observation) return opened;
|
|
361
|
+
const roundId = opened.observation.round.id;
|
|
362
|
+
const refreshed = await ctx.reload();
|
|
363
|
+
if (refreshed?.ok === false) return result('different', `Different: reload refused (${refreshed.error?.message || 'unknown'}).`);
|
|
364
|
+
const active = refreshed?.body?.activeRound || refreshed?.activeRound;
|
|
365
|
+
if (active?.id !== roundId) return result('different', 'Different: reload did not restore the open round.');
|
|
366
|
+
return review('Round is in progress after reload. Is the round shown as in progress?');
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
export const scenarioDefinitions = Object.freeze(definitions);
|
|
372
|
+
|
|
373
|
+
export function applicableScenarios(ctx) {
|
|
374
|
+
return scenarioDefinitions.filter((scenario) => {
|
|
375
|
+
try {
|
|
376
|
+
return scenario.applies(ctx);
|
|
377
|
+
} catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function createScenarioContext({ entries: scenarioEntries = [], limits = {}, walletDecimals = 8, setBalance, force, clearForce, armFault, clearFaults, reload, currentBalance, startingBalance, say, waitFor, skip, beforeScenario, beforeWait, awaitVerdict, cancelVerdict, resolveVerdict } = {}) {
|
|
384
|
+
const context = {
|
|
385
|
+
entries: scenarioEntries,
|
|
386
|
+
limits,
|
|
387
|
+
walletDecimals,
|
|
388
|
+
setBalance,
|
|
389
|
+
force,
|
|
390
|
+
clearForce,
|
|
391
|
+
reload,
|
|
392
|
+
currentBalance: currentBalance || (async () => undefined),
|
|
393
|
+
startingBalance,
|
|
394
|
+
say: say || (() => {}),
|
|
395
|
+
waitFor: waitFor || (async () => null),
|
|
396
|
+
armFault,
|
|
397
|
+
clearFaults,
|
|
398
|
+
skip,
|
|
399
|
+
beforeScenario,
|
|
400
|
+
beforeWait,
|
|
401
|
+
awaitVerdict,
|
|
402
|
+
cancelVerdict,
|
|
403
|
+
resolveVerdict,
|
|
404
|
+
};
|
|
405
|
+
context.stakeLimits = stakeLimits(context);
|
|
406
|
+
return context;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function clearForceSucceeded(response) {
|
|
410
|
+
return response?.ok === true && response.status === 204;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export async function runScenario(scenario, ctx, { timeoutMs = SCENARIO_TIMEOUT_MS } = {}) {
|
|
414
|
+
let start;
|
|
415
|
+
try {
|
|
416
|
+
start = await ctx.currentBalance();
|
|
417
|
+
} catch {
|
|
418
|
+
start = undefined;
|
|
419
|
+
}
|
|
420
|
+
const restore = start ?? ctx.startingBalance;
|
|
421
|
+
let outcome;
|
|
422
|
+
try {
|
|
423
|
+
outcome = await scenario.run(ctx, timeoutMs);
|
|
424
|
+
outcome ||= result('different', 'The scenario returned no result.');
|
|
425
|
+
} catch (error) {
|
|
426
|
+
outcome = result('different', error?.message || 'The scenario failed.');
|
|
427
|
+
}
|
|
428
|
+
const warnings = outcome?.warnings || [];
|
|
429
|
+
const warnForce = () => {
|
|
430
|
+
const warning = 'force still armed — the next bet may be forced';
|
|
431
|
+
warnings.push(warning);
|
|
432
|
+
if (outcome) outcome.detail = `${outcome.detail} · ${warning}`;
|
|
433
|
+
};
|
|
434
|
+
try {
|
|
435
|
+
ctx.clearFaults?.();
|
|
436
|
+
} catch {
|
|
437
|
+
// Cleanup continues even when the local fault wrapper is unavailable.
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const cleared = await ctx.clearForce?.();
|
|
441
|
+
if (!clearForceSucceeded(cleared)) warnForce();
|
|
442
|
+
} catch {
|
|
443
|
+
warnForce();
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
if (restore !== undefined && restore !== null) await ctx.setBalance(restore);
|
|
447
|
+
} catch {
|
|
448
|
+
// Session expiry may refuse the restoration.
|
|
449
|
+
}
|
|
450
|
+
try {
|
|
451
|
+
ctx.clearFaults?.();
|
|
452
|
+
} catch {
|
|
453
|
+
// Restoration never leaves a fault armed.
|
|
454
|
+
}
|
|
455
|
+
if (start === undefined && restore !== undefined && outcome) outcome.detail = `${outcome.detail} · server balance unavailable; restored sandbox starting balance`;
|
|
456
|
+
if (outcome) outcome.warnings = warnings;
|
|
457
|
+
return outcome || result('different', 'The scenario failed.');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function createScenarioRunner({ scenarios: selected, ctx, onScenario } = {}) {
|
|
461
|
+
const chosen = [...(selected || applicableScenarios(ctx || {}))].sort((a, b) => Number(a.id === 'session-expires') - Number(b.id === 'session-expires'));
|
|
462
|
+
let skipRequested = false;
|
|
463
|
+
let stopRequested = false;
|
|
464
|
+
let cancelWait = null;
|
|
465
|
+
let stoppedAt = null;
|
|
466
|
+
const runContext = () => ({
|
|
467
|
+
...ctx,
|
|
468
|
+
stopRequested: () => stopRequested,
|
|
469
|
+
skipRequested: () => skipRequested,
|
|
470
|
+
waitFor: (predicate, ms) => new Promise((resolve) => {
|
|
471
|
+
if (skipRequested || stopRequested) {
|
|
472
|
+
resolve(null);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
let finished = false;
|
|
476
|
+
let cancelUnderlying = null;
|
|
477
|
+
const finish = (value) => {
|
|
478
|
+
if (finished) return;
|
|
479
|
+
finished = true;
|
|
480
|
+
cancelWait = null;
|
|
481
|
+
resolve(value);
|
|
482
|
+
};
|
|
483
|
+
cancelWait = () => {
|
|
484
|
+
cancelUnderlying?.();
|
|
485
|
+
finish(null);
|
|
486
|
+
};
|
|
487
|
+
const pending = ctx.waitFor(predicate, ms, (cancel) => {
|
|
488
|
+
cancelUnderlying = cancel;
|
|
489
|
+
});
|
|
490
|
+
Promise.resolve(pending).then((value) => finish(skipRequested || stopRequested ? null : value), () => finish(null));
|
|
491
|
+
}),
|
|
492
|
+
});
|
|
493
|
+
return {
|
|
494
|
+
scenarios: chosen,
|
|
495
|
+
wasStopped: () => stoppedAt,
|
|
496
|
+
skip() {
|
|
497
|
+
skipRequested = true;
|
|
498
|
+
cancelWait?.();
|
|
499
|
+
ctx.cancelVerdict?.();
|
|
500
|
+
ctx.skip?.();
|
|
501
|
+
},
|
|
502
|
+
stop() {
|
|
503
|
+
stopRequested = true;
|
|
504
|
+
skipRequested = true;
|
|
505
|
+
cancelWait?.();
|
|
506
|
+
ctx.cancelVerdict?.();
|
|
507
|
+
ctx.skip?.();
|
|
508
|
+
},
|
|
509
|
+
async run(scenario) {
|
|
510
|
+
if (!scenario) return result('skipped', 'No scenario selected.');
|
|
511
|
+
skipRequested = false;
|
|
512
|
+
ctx.beforeScenario?.();
|
|
513
|
+
const outcome = await runScenario(scenario, runContext());
|
|
514
|
+
if (stopRequested) stoppedAt = 1;
|
|
515
|
+
onScenario?.({ scenario, outcome });
|
|
516
|
+
return outcome;
|
|
517
|
+
},
|
|
518
|
+
async runAll() {
|
|
519
|
+
const results = [];
|
|
520
|
+
for (const [index, scenario] of chosen.entries()) {
|
|
521
|
+
if (stopRequested) {
|
|
522
|
+
stoppedAt = index + 1;
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
skipRequested = false;
|
|
526
|
+
ctx.beforeScenario?.();
|
|
527
|
+
ctx.say?.(`${index + 1} / ${chosen.length} · ${scenario.title} — ${scenario.todo}`);
|
|
528
|
+
const outcome = await runScenario(scenario, runContext());
|
|
529
|
+
const item = { scenario, outcome };
|
|
530
|
+
results.push(item);
|
|
531
|
+
onScenario?.({ ...item, index, total: chosen.length });
|
|
532
|
+
if (outcome.warnings?.length) {
|
|
533
|
+
stopRequested = true;
|
|
534
|
+
ctx.say?.(`${scenario.title}: ${outcome.warnings.join('; ')}`);
|
|
535
|
+
}
|
|
536
|
+
if (outcome.status === 'needs-verdict' && !stopRequested) {
|
|
537
|
+
await ctx.awaitVerdict?.(item);
|
|
538
|
+
}
|
|
539
|
+
if (outcome.status === 'needs-verdict' && stopRequested) {
|
|
540
|
+
outcome.status = 'stopped';
|
|
541
|
+
outcome.detail = 'Stopped while waiting for a verdict.';
|
|
542
|
+
} else if (outcome.status === 'needs-verdict' && skipRequested) {
|
|
543
|
+
outcome.status = 'skipped';
|
|
544
|
+
outcome.detail = 'Skipped while waiting for a verdict.';
|
|
545
|
+
}
|
|
546
|
+
if (stopRequested) {
|
|
547
|
+
stoppedAt = index + 1;
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return results;
|
|
552
|
+
},
|
|
553
|
+
};
|
|
554
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell's side of the site's housegames bridge: the same messages, in
|
|
3
|
+
* the same shapes and order, that Coin Flip sends (coinflip-frontend
|
|
4
|
+
* src/core/bridge.js), so the site's header, round tracker and wallet
|
|
5
|
+
* reconciliation treat an engine game exactly like a House Original and
|
|
6
|
+
* need no code of their own for it.
|
|
7
|
+
*
|
|
8
|
+
* round start round-start, then balance {minus} debit shown at once
|
|
9
|
+
* settled balance {set} + round-end server figure known
|
|
10
|
+
* balance {add payout} + round-end + reconcile server figure deferred
|
|
11
|
+
* rollback balance {add stake, rollback} + round-end {rollback}
|
|
12
|
+
* unknown round-end + reconcile the site reads the wallet
|
|
13
|
+
*
|
|
14
|
+
* Messages go to the parent at its known origin only, never '*'. `gameId`
|
|
15
|
+
* is the catalog slug of the page, which the site matches exactly. The
|
|
16
|
+
* site's bar buttons come back as `housebets-game-control` once the game
|
|
17
|
+
* has said `controls-ready` (coinflip src/core/host-controls.js).
|
|
18
|
+
*/
|
|
19
|
+
const EV = {
|
|
20
|
+
roundStart: 'housebets-game-round-start',
|
|
21
|
+
roundEnd: 'housebets-game-round-end',
|
|
22
|
+
balance: 'housebets-game-balance',
|
|
23
|
+
reconcile: 'housebets-game-balance-reconciliation-request',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function createSiteBridge({ parent, parentOrigin, gameId, currencyId }) {
|
|
27
|
+
const post = (msg) => {
|
|
28
|
+
if (parent && parentOrigin) parent.postMessage(msg, parentOrigin);
|
|
29
|
+
};
|
|
30
|
+
// round-start / round-end / reconcile are flat; balance is nested, its
|
|
31
|
+
// own `type` being the currency (the site's receiver expects both).
|
|
32
|
+
const flat = (type, detail) => post({ ...detail, type });
|
|
33
|
+
const balance = (detail) => post({ type: EV.balance, payload: { type: currencyId, gameId, ...detail } });
|
|
34
|
+
const ids = (r) => ({ gameId, currencyId, roundId: r.corr, generation: r.generation });
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
roundStart(r) {
|
|
38
|
+
flat(EV.roundStart, { ...ids(r), amount: r.amount });
|
|
39
|
+
balance({ amount: r.amount, minus: true, roundId: r.corr, generation: r.generation });
|
|
40
|
+
},
|
|
41
|
+
roundEnd(r) {
|
|
42
|
+
const end = { ...ids(r), amount: r.amount };
|
|
43
|
+
const round = { roundId: r.corr, generation: r.generation, moment: true };
|
|
44
|
+
switch (r.outcome) {
|
|
45
|
+
case 'settled':
|
|
46
|
+
if (r.balance !== null) {
|
|
47
|
+
balance({ ...round, amount: r.balance, set: true, payout: r.payout });
|
|
48
|
+
flat(EV.roundEnd, end);
|
|
49
|
+
} else {
|
|
50
|
+
balance({ ...round, amount: r.payout, add: true, payout: r.payout });
|
|
51
|
+
flat(EV.roundEnd, end);
|
|
52
|
+
flat(EV.reconcile, ids(r));
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
case 'rollback':
|
|
56
|
+
balance({ ...round, amount: r.amount, add: true, rollback: true });
|
|
57
|
+
flat(EV.roundEnd, { ...end, rollback: true });
|
|
58
|
+
return;
|
|
59
|
+
default: // unknown
|
|
60
|
+
flat(EV.roundEnd, end);
|
|
61
|
+
flat(EV.reconcile, ids(r));
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
ready: () => {
|
|
65
|
+
post({ message: 'gameLoaded', data: { progress: 100 } });
|
|
66
|
+
post({ type: 'housebets-game-controls-ready' });
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* The site's bar and bet history: onControl gets the whole
|
|
70
|
+
* `housebets-game-control` envelope, as the Originals read it
|
|
71
|
+
* (limbo host-controls.js) — a bare `{action}` from the bar ('sound',
|
|
72
|
+
* 'speed', 'settings', 'rules', 'provably-fair'), the page's report
|
|
73
|
+
* of the player's own trusted press or key on it ('site-input', for
|
|
74
|
+
* the click gate), or the history's
|
|
75
|
+
* `{action: 'provably-fair', gameType, roundId, requestId}` and its
|
|
76
|
+
* `provably-fair-cancel`, whose round fields the reader needs whole.
|
|
77
|
+
* From the parent at its origin only: the game frame cannot send one.
|
|
78
|
+
*/
|
|
79
|
+
listen(onControl, self = window) {
|
|
80
|
+
self.addEventListener('message', (e) => {
|
|
81
|
+
if (e.source !== parent || e.origin !== parentOrigin) return;
|
|
82
|
+
const d = e.data;
|
|
83
|
+
if (d && typeof d === 'object' && d.type === 'housebets-game-control' && typeof d.action === 'string') onControl(d);
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|