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,242 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en": {
|
|
3
|
+
"freeSpins": "Free spins {i} of {n}",
|
|
4
|
+
"winsTimes": "wins ×{x}",
|
|
5
|
+
"tapToSkip": "tap to skip",
|
|
6
|
+
"freePaid": "Free spins paid {w}",
|
|
7
|
+
"buyHint": "Free spins for {n} spins: the stake is the price",
|
|
8
|
+
"maxWin": "max win",
|
|
9
|
+
"modes": {
|
|
10
|
+
"base": "Spin",
|
|
11
|
+
"buy": "Buy free spins"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"ar": {
|
|
15
|
+
"freeSpins": "دورات مجانية {i} من {n}",
|
|
16
|
+
"winsTimes": "الأرباح ×{x}",
|
|
17
|
+
"tapToSkip": "اضغط للتخطي",
|
|
18
|
+
"freePaid": "دفعت الدورات المجانية {w}",
|
|
19
|
+
"buyHint": "دورات مجانية مقابل {n} دورة: الرهان هو السعر",
|
|
20
|
+
"maxWin": "أقصى ربح",
|
|
21
|
+
"modes": {
|
|
22
|
+
"base": "دوران",
|
|
23
|
+
"buy": "شراء الدورات المجانية"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"da": {
|
|
27
|
+
"freeSpins": "Gratis spins {i} af {n}",
|
|
28
|
+
"winsTimes": "gevinster ×{x}",
|
|
29
|
+
"tapToSkip": "tryk for at springe over",
|
|
30
|
+
"freePaid": "Gratis spins betalte {w}",
|
|
31
|
+
"buyHint": "Gratis spins for {n} spins: indsatsen er prisen",
|
|
32
|
+
"maxWin": "maks. gevinst",
|
|
33
|
+
"modes": {
|
|
34
|
+
"base": "Spin",
|
|
35
|
+
"buy": "Køb gratis spins"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"de": {
|
|
39
|
+
"freeSpins": "Freispiele {i} von {n}",
|
|
40
|
+
"winsTimes": "Gewinne ×{x}",
|
|
41
|
+
"tapToSkip": "zum Überspringen tippen",
|
|
42
|
+
"freePaid": "Freispiele zahlten {w}",
|
|
43
|
+
"buyHint": "Freispiele für {n} Spins: der Einsatz ist der Preis",
|
|
44
|
+
"maxWin": "Maximalgewinn",
|
|
45
|
+
"modes": {
|
|
46
|
+
"base": "Drehen",
|
|
47
|
+
"buy": "Freispiele kaufen"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"es": {
|
|
51
|
+
"freeSpins": "Giros gratis {i} de {n}",
|
|
52
|
+
"winsTimes": "premios ×{x}",
|
|
53
|
+
"tapToSkip": "toca para saltar",
|
|
54
|
+
"freePaid": "Los giros gratis pagaron {w}",
|
|
55
|
+
"buyHint": "Giros gratis por {n} giros: la apuesta es el precio",
|
|
56
|
+
"maxWin": "premio máximo",
|
|
57
|
+
"modes": {
|
|
58
|
+
"base": "Girar",
|
|
59
|
+
"buy": "Comprar giros gratis"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"fi": {
|
|
63
|
+
"freeSpins": "Ilmaiskierrokset {i}/{n}",
|
|
64
|
+
"winsTimes": "voitot ×{x}",
|
|
65
|
+
"tapToSkip": "ohita napauttamalla",
|
|
66
|
+
"freePaid": "Ilmaiskierrokset maksoivat {w}",
|
|
67
|
+
"buyHint": "Ilmaiskierrokset {n} kierroksen hinnalla: panos on hinta",
|
|
68
|
+
"maxWin": "maksimivoitto",
|
|
69
|
+
"modes": {
|
|
70
|
+
"base": "Pyöräytä",
|
|
71
|
+
"buy": "Osta ilmaiskierrokset"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"fr": {
|
|
75
|
+
"freeSpins": "Tours gratuits {i} sur {n}",
|
|
76
|
+
"winsTimes": "gains ×{x}",
|
|
77
|
+
"tapToSkip": "touchez pour passer",
|
|
78
|
+
"freePaid": "Les tours gratuits ont payé {w}",
|
|
79
|
+
"buyHint": "Tours gratuits pour {n} tours : la mise est le prix",
|
|
80
|
+
"maxWin": "gain maximal",
|
|
81
|
+
"modes": {
|
|
82
|
+
"base": "Lancer",
|
|
83
|
+
"buy": "Acheter les tours gratuits"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"hi": {
|
|
87
|
+
"freeSpins": "फ्री स्पिन {i} / {n}",
|
|
88
|
+
"winsTimes": "जीत ×{x}",
|
|
89
|
+
"tapToSkip": "छोड़ने के लिए टैप करें",
|
|
90
|
+
"freePaid": "फ्री स्पिन से {w} मिला",
|
|
91
|
+
"buyHint": "{n} स्पिन की कीमत पर फ्री स्पिन: दांव ही कीमत है",
|
|
92
|
+
"maxWin": "अधिकतम जीत",
|
|
93
|
+
"modes": {
|
|
94
|
+
"base": "स्पिन",
|
|
95
|
+
"buy": "फ्री स्पिन खरीदें"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"hu": {
|
|
99
|
+
"freeSpins": "Ingyenes pörgetés {i}/{n}",
|
|
100
|
+
"winsTimes": "nyeremények ×{x}",
|
|
101
|
+
"tapToSkip": "koppints az átugráshoz",
|
|
102
|
+
"freePaid": "Az ingyenes pörgetések {w} fizettek",
|
|
103
|
+
"buyHint": "Ingyenes pörgetések {n} pörgetés áráért: a tét az ár",
|
|
104
|
+
"maxWin": "max. nyeremény",
|
|
105
|
+
"modes": {
|
|
106
|
+
"base": "Pörgetés",
|
|
107
|
+
"buy": "Ingyenes pörgetések vásárlása"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"id": {
|
|
111
|
+
"freeSpins": "Putaran gratis {i} dari {n}",
|
|
112
|
+
"winsTimes": "kemenangan ×{x}",
|
|
113
|
+
"tapToSkip": "ketuk untuk lewati",
|
|
114
|
+
"freePaid": "Putaran gratis membayar {w}",
|
|
115
|
+
"buyHint": "Putaran gratis seharga {n} putaran: taruhan adalah harganya",
|
|
116
|
+
"maxWin": "kemenangan maks",
|
|
117
|
+
"modes": {
|
|
118
|
+
"base": "Putar",
|
|
119
|
+
"buy": "Beli putaran gratis"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"it": {
|
|
123
|
+
"freeSpins": "Giri gratis {i} di {n}",
|
|
124
|
+
"winsTimes": "vincite ×{x}",
|
|
125
|
+
"tapToSkip": "tocca per saltare",
|
|
126
|
+
"freePaid": "I giri gratis hanno pagato {w}",
|
|
127
|
+
"buyHint": "Giri gratis per {n} giri: la puntata è il prezzo",
|
|
128
|
+
"maxWin": "vincita massima",
|
|
129
|
+
"modes": {
|
|
130
|
+
"base": "Gira",
|
|
131
|
+
"buy": "Compra giri gratis"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"ja": {
|
|
135
|
+
"freeSpins": "フリースピン {i}/{n}",
|
|
136
|
+
"winsTimes": "配当 ×{x}",
|
|
137
|
+
"tapToSkip": "タップでスキップ",
|
|
138
|
+
"freePaid": "フリースピン配当 {w}",
|
|
139
|
+
"buyHint": "{n}スピン分でフリースピン購入:ベット額が価格です",
|
|
140
|
+
"maxWin": "最大配当",
|
|
141
|
+
"modes": {
|
|
142
|
+
"base": "スピン",
|
|
143
|
+
"buy": "フリースピン購入"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"ko": {
|
|
147
|
+
"freeSpins": "프리 스핀 {i}/{n}",
|
|
148
|
+
"winsTimes": "당첨 ×{x}",
|
|
149
|
+
"tapToSkip": "탭하여 건너뛰기",
|
|
150
|
+
"freePaid": "프리 스핀 당첨 {w}",
|
|
151
|
+
"buyHint": "{n}스핀 가격으로 프리 스핀: 베팅액이 가격입니다",
|
|
152
|
+
"maxWin": "최대 당첨",
|
|
153
|
+
"modes": {
|
|
154
|
+
"base": "스핀",
|
|
155
|
+
"buy": "프리 스핀 구매"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"pl": {
|
|
159
|
+
"freeSpins": "Darmowe spiny {i} z {n}",
|
|
160
|
+
"winsTimes": "wygrane ×{x}",
|
|
161
|
+
"tapToSkip": "dotknij, aby pominąć",
|
|
162
|
+
"freePaid": "Darmowe spiny wypłaciły {w}",
|
|
163
|
+
"buyHint": "Darmowe spiny za {n} spinów: stawka to cena",
|
|
164
|
+
"maxWin": "maks. wygrana",
|
|
165
|
+
"modes": {
|
|
166
|
+
"base": "Spin",
|
|
167
|
+
"buy": "Kup darmowe spiny"
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"pt": {
|
|
171
|
+
"freeSpins": "Giros grátis {i} de {n}",
|
|
172
|
+
"winsTimes": "ganhos ×{x}",
|
|
173
|
+
"tapToSkip": "toque para pular",
|
|
174
|
+
"freePaid": "Os giros grátis pagaram {w}",
|
|
175
|
+
"buyHint": "Giros grátis por {n} giros: a aposta é o preço",
|
|
176
|
+
"maxWin": "ganho máximo",
|
|
177
|
+
"modes": {
|
|
178
|
+
"base": "Girar",
|
|
179
|
+
"buy": "Comprar giros grátis"
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"ru": {
|
|
183
|
+
"freeSpins": "Бесплатные вращения {i} из {n}",
|
|
184
|
+
"winsTimes": "выигрыши ×{x}",
|
|
185
|
+
"tapToSkip": "нажмите, чтобы пропустить",
|
|
186
|
+
"freePaid": "Бесплатные вращения принесли {w}",
|
|
187
|
+
"buyHint": "Бесплатные вращения за {n} спинов: ставка — это цена",
|
|
188
|
+
"maxWin": "максимальный выигрыш",
|
|
189
|
+
"modes": {
|
|
190
|
+
"base": "Вращение",
|
|
191
|
+
"buy": "Купить бесплатные вращения"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"th": {
|
|
195
|
+
"freeSpins": "ฟรีสปิน {i} จาก {n}",
|
|
196
|
+
"winsTimes": "รางวัล ×{x}",
|
|
197
|
+
"tapToSkip": "แตะเพื่อข้าม",
|
|
198
|
+
"freePaid": "ฟรีสปินจ่าย {w}",
|
|
199
|
+
"buyHint": "ฟรีสปินในราคา {n} สปิน: เงินเดิมพันคือราคา",
|
|
200
|
+
"maxWin": "รางวัลสูงสุด",
|
|
201
|
+
"modes": {
|
|
202
|
+
"base": "หมุน",
|
|
203
|
+
"buy": "ซื้อฟรีสปิน"
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"tr": {
|
|
207
|
+
"freeSpins": "Bedava dönüş {i}/{n}",
|
|
208
|
+
"winsTimes": "kazançlar ×{x}",
|
|
209
|
+
"tapToSkip": "geçmek için dokun",
|
|
210
|
+
"freePaid": "Bedava dönüşler {w} ödedi",
|
|
211
|
+
"buyHint": "{n} dönüş fiyatına bedava dönüşler: bahis fiyattır",
|
|
212
|
+
"maxWin": "maksimum kazanç",
|
|
213
|
+
"modes": {
|
|
214
|
+
"base": "Çevir",
|
|
215
|
+
"buy": "Bedava dönüş satın al"
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
"vi": {
|
|
219
|
+
"freeSpins": "Lượt quay miễn phí {i}/{n}",
|
|
220
|
+
"winsTimes": "thắng ×{x}",
|
|
221
|
+
"tapToSkip": "chạm để bỏ qua",
|
|
222
|
+
"freePaid": "Lượt quay miễn phí trả {w}",
|
|
223
|
+
"buyHint": "Lượt quay miễn phí với giá {n} lượt: tiền cược là giá",
|
|
224
|
+
"maxWin": "thắng tối đa",
|
|
225
|
+
"modes": {
|
|
226
|
+
"base": "Quay",
|
|
227
|
+
"buy": "Mua lượt quay miễn phí"
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
"zh": {
|
|
231
|
+
"freeSpins": "免费旋转 {i}/{n}",
|
|
232
|
+
"winsTimes": "奖金 ×{x}",
|
|
233
|
+
"tapToSkip": "点击跳过",
|
|
234
|
+
"freePaid": "免费旋转赢得 {w}",
|
|
235
|
+
"buyHint": "以 {n} 次旋转的价格购买免费旋转:投注额即价格",
|
|
236
|
+
"maxWin": "最高奖金",
|
|
237
|
+
"modes": {
|
|
238
|
+
"base": "旋转",
|
|
239
|
+
"buy": "购买免费旋转"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
|
+
<title>Lines</title>
|
|
7
|
+
<link rel="stylesheet" href="./game.css">
|
|
8
|
+
<script type="module" src="/engine-sdk/bet-field.js"></script>
|
|
9
|
+
<script type="module" src="./game.js"></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<hb-game id="game">
|
|
13
|
+
<div slot="zone" class="slot" id="slot">
|
|
14
|
+
<div class="banner" id="banner" aria-live="polite"></div>
|
|
15
|
+
<div class="board">
|
|
16
|
+
<div class="reels" id="reels"></div>
|
|
17
|
+
<svg class="lines" id="lines" aria-hidden="true"></svg>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="win" id="win" aria-live="polite"></div>
|
|
20
|
+
</div>
|
|
21
|
+
</hb-game>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"rows":3,"reels":[["K","A","J","seven","J","J","S","Q","Q","J","Q","gem","W","J","Q","K","Q","seven","bell","bell","star","A","K","star","J","K","A","bell","A","A","K","Q","W","star","gem"],["J","W","Q","J","seven","S","Q","A","K","W","star","bell","K","star","Q","K","J","K","bell","A","A","Q","Q","J","seven","K","star","gem","A","A","bell","gem","J","J","Q"],["bell","gem","A","K","star","seven","Q","bell","seven","Q","Q","star","star","W","A","W","Q","A","A","K","J","Q","K","bell","A","gem","S","K","J","J","K","J","J","J","Q"],["Q","K","bell","Q","gem","J","star","Q","gem","K","seven","bell","star","bell","J","A","A","W","A","J","K","K","J","S","J","A","A","J","star","Q","K","Q","Q","W","seven"],["Q","K","Q","J","J","A","W","Q","Q","star","J","Q","K","J","Q","bell","A","seven","K","A","K","bell","seven","K","A","A","star","gem","J","J","star","W","S","gem","bell"]],"freeReels":[["K","A","K","Q","bell","gem","J","J","bell","gem","star","star","J","J","bell","J","star","Q","W","K","Q","seven","A","Q","Q","A","Q","seven","K","A","W","S","W","A","J","K"],["A","Q","gem","Q","J","Q","bell","star","star","Q","S","gem","J","A","K","J","W","Q","star","seven","K","bell","A","A","K","W","Q","K","A","K","seven","J","bell","W","J","J"],["K","Q","seven","A","J","W","star","J","W","Q","K","seven","S","A","K","K","bell","gem","A","K","bell","A","bell","Q","J","star","Q","star","gem","Q","J","J","A","J","Q","W"],["A","K","J","star","Q","A","W","K","Q","gem","J","K","star","J","A","J","A","bell","seven","W","A","bell","K","Q","Q","star","W","K","S","seven","J","Q","bell","gem","J","Q"],["bell","J","A","gem","A","seven","J","Q","W","J","star","W","bell","W","K","J","A","K","A","K","Q","Q","K","seven","J","star","bell","Q","gem","A","K","S","J","Q","Q","star"]],"lines":[[1,1,1,1,1],[0,0,0,0,0],[2,2,2,2,2],[0,1,2,1,0],[2,1,0,1,2],[0,0,1,2,2],[2,2,1,0,0],[1,0,0,0,1],[1,2,2,2,1],[1,0,1,2,1]],"pays":{"seven":{"3":40000,"4":160000,"5":800000},"gem":{"3":32000,"4":120000,"5":480000},"star":{"3":24000,"4":80000,"5":320000},"bell":{"3":16000,"4":64000,"5":200000},"A":{"3":8000,"4":32000,"5":120000},"K":{"3":6400,"4":24000,"5":96000},"Q":{"3":4800,"4":20000,"5":80000},"J":{"3":4000,"4":16000,"5":64000},"W":{"3":80000,"4":400000,"5":1600000}},"wild":"W","scatter":{"symbol":"S","pays":{"3":20000,"4":100000,"5":500000},"spins":{"3":10,"4":12,"5":15},"retrigger":{"3":5,"4":5,"5":5},"multiplier":2,"cap":60},"maxWinBp":50000000,"price":27}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The lines slot's maths: five reels, ten lines, a wild, a scatter that
|
|
3
|
+
// starts ten free spins at 2× (retriggers +5), a 5,000× cap. The maths kit
|
|
4
|
+
// plays 400,000 rounds into rows and fits the table to exactly 96.00%. The
|
|
5
|
+
// reels, lines and pays go to the bundle too, so the page draws what pays.
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { balance, fit, rng, writePack } from '../math-kit/pack.mjs';
|
|
10
|
+
import { books, slot } from '../math-kit/slot.mjs';
|
|
11
|
+
|
|
12
|
+
const DIR = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const project = join(DIR, '..', 'hbengine.json');
|
|
14
|
+
const GAME = process.argv[2] || (existsSync(project) ? JSON.parse(readFileSync(project, 'utf8')).slug : 'lines'), RTP = 9600, ROUNDS = Number(process.env.ROUNDS || 400_000), SEED = 20261015;
|
|
15
|
+
|
|
16
|
+
// One strip per reel: symbols repeated by weight.
|
|
17
|
+
const strip = (w) => Object.entries(w).flatMap(([s, n]) => Array(n).fill(s));
|
|
18
|
+
const shuffle = (a, next) => { for (let i = a.length - 1; i > 0; i--) { const j = next.int(i + 1); [a[i], a[j]] = [a[j], a[i]]; } return a; };
|
|
19
|
+
const weights = { gem: 2, star: 3, bell: 3, seven: 2, A: 5, K: 5, Q: 6, J: 6, W: 2, S: 1 };
|
|
20
|
+
const freeWeights = { ...weights, W: 3 };
|
|
21
|
+
const order = rng(7);
|
|
22
|
+
const model = {
|
|
23
|
+
rows: 3,
|
|
24
|
+
reels: [0, 1, 2, 3, 4].map(() => shuffle(strip(weights), order)),
|
|
25
|
+
freeReels: [0, 1, 2, 3, 4].map(() => shuffle(strip(freeWeights), order)),
|
|
26
|
+
lines: [[1, 1, 1, 1, 1], [0, 0, 0, 0, 0], [2, 2, 2, 2, 2], [0, 1, 2, 1, 0], [2, 1, 0, 1, 2], [0, 0, 1, 2, 2], [2, 2, 1, 0, 0], [1, 0, 0, 0, 1], [1, 2, 2, 2, 1], [1, 0, 1, 2, 1]],
|
|
27
|
+
// line pays for 3, 4, 5 in units of 0.08× the stake; scatters (below) in basis points of the stake
|
|
28
|
+
pays: Object.fromEntries(Object.entries({
|
|
29
|
+
seven: [50, 200, 1000], gem: [40, 150, 600], star: [30, 100, 400], bell: [20, 80, 250],
|
|
30
|
+
A: [10, 40, 150], K: [8, 30, 120], Q: [6, 25, 100], J: [5, 20, 80], W: [100, 500, 2000],
|
|
31
|
+
}).map(([s, [a, b, c]]) => [s, { 3: a * 800, 4: b * 800, 5: c * 800 }])),
|
|
32
|
+
wild: 'W',
|
|
33
|
+
scatter: { symbol: 'S', pays: { 3: 20000, 4: 100000, 5: 500000 }, spins: { 3: 10, 4: 12, 5: 15 }, retrigger: { 3: 5, 4: 5, 5: 5 }, multiplier: 2, cap: 60 },
|
|
34
|
+
maxWinBp: 50_000_000,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const rows = books(slot(model), rng(SEED), ROUNDS);
|
|
38
|
+
const raw = rows.reduce((a, r) => [a[0] + r.weight * BigInt(r.pay.spin), a[1] + r.weight], [0n, 0n]);
|
|
39
|
+
const hits = rows.reduce((a, r) => a + (r.pay.spin > 0 ? r.weight : 0n), 0n);
|
|
40
|
+
console.log(`played ${ROUNDS}: before the fit RTP ${(Number(raw[0] * 1000000n / raw[1]) / 1e6 / 100).toFixed(2)}%, hit rate ${(Number(hits * 10000n / BigInt(ROUNDS)) / 100).toFixed(2)}%`);
|
|
41
|
+
// Bonus buy: the free spins alone. The stake is the price; the price is what
|
|
42
|
+
// the free spins return on average over the target, in whole spins, so the
|
|
43
|
+
// balance to exactly 96.00% only nudges. A bought round pays its free
|
|
44
|
+
// spins' win per stake, rounded down to the basis point.
|
|
45
|
+
const freeRounds = books(slot(model), rng(SEED + 1), ROUNDS / 8, 4000, true);
|
|
46
|
+
const mean = freeRounds.reduce((a, r) => [a[0] + r.weight * BigInt(r.pay.spin), a[1] + r.weight], [0n, 0n]);
|
|
47
|
+
const PRICE = Math.round(Number(mean[0] / mean[1]) / RTP);
|
|
48
|
+
const bought = freeRounds.map((r) => ({ ...r, pay: { buy: Math.floor(r.pay.spin / PRICE) } }));
|
|
49
|
+
console.log(`free spins return ${(Number(mean[0] / mean[1]) / 10000).toFixed(2)} spins on average: the buy costs ${PRICE} spins`);
|
|
50
|
+
writePack(join(DIR), { game: GAME, rtpBp: RTP, modes: [
|
|
51
|
+
{ id: 'base', label: 'Spin', rows: fit(rows, RTP), bets: [{ id: 'spin', label: 'Spin' }] },
|
|
52
|
+
{ id: 'buy', label: `Buy free spins (${PRICE}×)`, rows: balance(bought, RTP), bets: [{ id: 'buy', label: 'Buy' }] },
|
|
53
|
+
] });
|
|
54
|
+
writeFileSync(join(DIR, '..', 'bundle', 'model.json'), JSON.stringify({ ...model, price: PRICE }) + '\n');
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The maths kit: a game's outcomes as weighted rows, its RTP exactly, and the
|
|
3
|
+
* pack the engine verifies (SPEC §1). A row is { weight, data, pay } where
|
|
4
|
+
* pay is { betId: multBp } (10000 = 1×, the total stake back) and data is
|
|
5
|
+
* what the bundle receives to show the round.
|
|
6
|
+
*
|
|
7
|
+
* import { writePack, fit } from './pack.mjs';
|
|
8
|
+
* const rows = fit(simulated, 9600); // exact 96.00% (balance() when no row pays nothing)
|
|
9
|
+
* writePack('math', { game: 'my-slot', rtpBp: 9600, modes: [{ id: 'base', rows, bets: ['spin'] }] });
|
|
10
|
+
*
|
|
11
|
+
* Every number here is an integer: weights are BigInt, RTP is a fraction.
|
|
12
|
+
*/
|
|
13
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
|
|
16
|
+
const MAX_SUM = 1n << 63n;
|
|
17
|
+
|
|
18
|
+
// A small seeded generator for building tables (never for play: the engine draws).
|
|
19
|
+
export function rng(seed) {
|
|
20
|
+
let a = seed >>> 0;
|
|
21
|
+
const next = () => {
|
|
22
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
23
|
+
let t = a;
|
|
24
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
25
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
26
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
27
|
+
};
|
|
28
|
+
next.int = (n) => Math.floor(next() * n);
|
|
29
|
+
return next;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function binomial(n, k) {
|
|
33
|
+
let r = 1n;
|
|
34
|
+
for (let i = 1n; i <= BigInt(k); i++) r = (r * BigInt(n - k) + r * i) / i;
|
|
35
|
+
return r;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const gcd = (a, b) => { while (b) [a, b] = [b, a % b]; return a < 0n ? -a : a; };
|
|
39
|
+
|
|
40
|
+
/** RTP of one bet over rows, exactly: { num, den } in basis points, and a 4-decimal string. */
|
|
41
|
+
export function rtp(rows, bet) {
|
|
42
|
+
let num = 0n, den = 0n;
|
|
43
|
+
for (const r of rows) { den += r.weight; num += r.weight * BigInt(r.pay[bet] || 0); }
|
|
44
|
+
return { num, den, bp: bp4(num, den) };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const bp4 = (num, den) => {
|
|
48
|
+
const x = (num * 10000n) / den; // bp × 10^4, truncated as the verifier reports it
|
|
49
|
+
return `${x / 10000n}.${String(x % 10000n).padStart(4, '0')}`;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Weights so that one bet's RTP is exactly targetBp: the paying rows keep
|
|
54
|
+
* their proportions (all multiplied by one integer), the rows that pay
|
|
55
|
+
* nothing share what is left in their own proportions. Needs rows that pay
|
|
56
|
+
* nothing, and paying rows that alone return more than the target.
|
|
57
|
+
*/
|
|
58
|
+
export function fit(rows, targetBp, bet = Object.keys(rows[0].pay)[0]) {
|
|
59
|
+
const T = BigInt(targetBp);
|
|
60
|
+
const wins = rows.filter((r) => r.pay[bet] > 0), zeros = rows.filter((r) => !(r.pay[bet] > 0));
|
|
61
|
+
if (!zeros.length) throw new Error('fit: no row pays nothing');
|
|
62
|
+
const S = wins.reduce((s, r) => s + r.weight * (BigInt(r.pay[bet]) - T), 0n); // what the wins return above T
|
|
63
|
+
if (S <= 0n) throw new Error(`fit: the paying rows alone return less than ${targetBp / 100}%`);
|
|
64
|
+
const k = T / gcd(S, T); // so that k·S is a multiple of T
|
|
65
|
+
const Z = (k * S) / T; // the weight the zero rows share
|
|
66
|
+
const zw = zeros.reduce((s, r) => s + r.weight, 0n);
|
|
67
|
+
let given = 0n;
|
|
68
|
+
const out = rows.map((r) => {
|
|
69
|
+
if (r.pay[bet] > 0) return { ...r, weight: r.weight * k };
|
|
70
|
+
const w = (Z * r.weight) / zw;
|
|
71
|
+
given += w;
|
|
72
|
+
return { ...r, weight: w };
|
|
73
|
+
});
|
|
74
|
+
// The rounding remainder goes to the first zero row; a zero weight is dropped.
|
|
75
|
+
const first = out.find((r) => !(r.pay[bet] > 0));
|
|
76
|
+
first.weight += Z - given;
|
|
77
|
+
const kept = out.filter((r) => r.weight > 0n);
|
|
78
|
+
const check = rtp(kept, bet);
|
|
79
|
+
if (check.num !== T * check.den) throw new Error('fit: not exact (a bug)');
|
|
80
|
+
return kept;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Weights so that one bet's RTP is exactly targetBp when every row pays
|
|
85
|
+
* something (a bonus buy, plinko): rows paying more than the target are
|
|
86
|
+
* all multiplied by one integer, rows paying less by another, so each
|
|
87
|
+
* group keeps its own proportions and the two balance at the target.
|
|
88
|
+
*/
|
|
89
|
+
export function balance(rows, targetBp, bet = Object.keys(rows[0].pay)[0]) {
|
|
90
|
+
const T = BigInt(targetBp);
|
|
91
|
+
const over = rows.reduce((s, r) => s + (BigInt(r.pay[bet] || 0) > T ? r.weight * (BigInt(r.pay[bet]) - T) : 0n), 0n);
|
|
92
|
+
const under = rows.reduce((s, r) => s + (BigInt(r.pay[bet] || 0) < T ? r.weight * (T - BigInt(r.pay[bet] || 0)) : 0n), 0n);
|
|
93
|
+
if (!over || !under) throw new Error('balance: needs rows on both sides of the target');
|
|
94
|
+
const g = gcd(over, under);
|
|
95
|
+
const out = rows.map((r) => ({ ...r, weight: r.weight * (BigInt(r.pay[bet] || 0) > T ? under / g : BigInt(r.pay[bet] || 0) < T ? over / g : 1n) }));
|
|
96
|
+
const check = rtp(out, bet);
|
|
97
|
+
if (check.num !== T * check.den) throw new Error('balance: not exact (a bug)');
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Rows with the same data are one row with the sum of their weights. */
|
|
102
|
+
export function merge(rows) {
|
|
103
|
+
const by = new Map();
|
|
104
|
+
for (const r of rows) {
|
|
105
|
+
const key = JSON.stringify(r.data);
|
|
106
|
+
const x = by.get(key);
|
|
107
|
+
if (x) x.weight += r.weight;
|
|
108
|
+
else by.set(key, { ...r, weight: r.weight });
|
|
109
|
+
}
|
|
110
|
+
return [...by.values()];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Inclusive [lo, hi] ranges of sorted indexes.
|
|
114
|
+
function ranges(ids) {
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const i of ids) {
|
|
117
|
+
const last = out.at(-1);
|
|
118
|
+
if (last && last[1] === i - 1) last[1] = i;
|
|
119
|
+
else out.push([i, i]);
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Writes manifest.json and one table per mode. A mode is { id, label?, rows,
|
|
126
|
+
* bets: [id | {id, label}] }; its rows are sorted by what the first bet pays,
|
|
127
|
+
* so each pay tier is one range. Prints every bet's exact RTP, hit rate and
|
|
128
|
+
* largest multiplier; returns them.
|
|
129
|
+
*/
|
|
130
|
+
export function writePack(dir, { game, rtpBp, modes }) {
|
|
131
|
+
mkdirSync(dir, { recursive: true });
|
|
132
|
+
const manifest = { format: 'house-engine-math/1', game, rtpBp, modes: [] };
|
|
133
|
+
const report = [];
|
|
134
|
+
for (const mode of modes) {
|
|
135
|
+
const bets = mode.bets.map((b) => (typeof b === 'string' ? { id: b } : b));
|
|
136
|
+
const first = bets[0].id;
|
|
137
|
+
const rows = [...mode.rows].sort((a, b) => (a.pay[first] || 0) - (b.pay[first] || 0));
|
|
138
|
+
if (rows.length < 2 || rows.length > 1_000_000) throw new Error(`${mode.id}: 2 to 1,000,000 rows, not ${rows.length}`);
|
|
139
|
+
const sum = rows.reduce((s, r) => s + r.weight, 0n);
|
|
140
|
+
if (sum > MAX_SUM) throw new Error(`${mode.id}: weights sum to more than 2^63`);
|
|
141
|
+
const lines = rows.map((r) => {
|
|
142
|
+
if (r.weight <= 0n) throw new Error(`${mode.id}: a weight is not positive`);
|
|
143
|
+
const json = r.data === undefined ? '' : JSON.stringify(r.data);
|
|
144
|
+
if (json.length > 4096) throw new Error(`${mode.id}: a row's data is over 4096 bytes`);
|
|
145
|
+
return json ? `${r.weight}\t${json}` : String(r.weight);
|
|
146
|
+
});
|
|
147
|
+
const table = `${game}-${mode.id}.tsv`;
|
|
148
|
+
writeFileSync(join(dir, table), lines.join('\n') + '\n');
|
|
149
|
+
manifest.modes.push({
|
|
150
|
+
id: mode.id, ...(mode.label ? { label: mode.label } : {}), kind: 'table', table,
|
|
151
|
+
bets: bets.map((b) => {
|
|
152
|
+
const tiers = new Map();
|
|
153
|
+
rows.forEach((r, i) => { const m = r.pay[b.id] || 0; if (m > 0) (tiers.get(m) || tiers.set(m, []).get(m)).push(i); });
|
|
154
|
+
const { num, den, bp } = rtp(rows, b.id);
|
|
155
|
+
const hit = rows.reduce((s, r) => s + (r.pay[b.id] > 0 ? r.weight : 0n), 0n);
|
|
156
|
+
const max = Math.max(...tiers.keys());
|
|
157
|
+
report.push({ mode: mode.id, bet: b.id, rtp: bp, hit: bp4(hit * 10000n, den), max: max / 10000, rows: rows.length, exact: num % den === 0n });
|
|
158
|
+
return { id: b.id, ...(b.label ? { label: b.label } : {}), tiers: [...tiers].map(([multBp, ids]) => ({ outcomes: ranges(ids), multBp })) };
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
writeFileSync(join(dir, 'manifest.json'), JSON.stringify(manifest) + '\n');
|
|
163
|
+
for (const r of report) console.log(`${r.mode}/${r.bet}: RTP ${r.rtp} bp hit ${r.hit} bp max ${r.max}× rows ${r.rows}`);
|
|
164
|
+
return report;
|
|
165
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@housegames/engine-math",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "The maths kit: outcomes as weighted rows, exact RTP, an exact fit to a target, the pack the engine verifies; a video slot model (reels, lines, wilds, scatters, free spins) and a tumble model (scatter pays, tumbles, multipliers).",
|
|
7
|
+
"exports": { "./pack.mjs": "./pack.mjs", "./slot.mjs": "./slot.mjs", "./tumble.mjs": "./tumble.mjs" }
|
|
8
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A video slot for the maths kit: reels, paylines, a wild, a scatter that
|
|
3
|
+
* starts free spins (with a multiplier, retriggers), and a win cap. It plays
|
|
4
|
+
* rounds with a seeded generator to make the table's rows ("books"): every
|
|
5
|
+
* row is a whole round, base spin and free spins, with what it pays. fit()
|
|
6
|
+
* then makes the table's RTP exact; the table is the game.
|
|
7
|
+
*
|
|
8
|
+
* Pays are in basis points of the total stake (10000 = 1×) for 3, 4, 5 of a
|
|
9
|
+
* kind on a line, left to right; scatters pay anywhere, times the stake.
|
|
10
|
+
*
|
|
11
|
+
* A row's data, for the bundle: { b: spin, f: [spin…], x: free multiplier }
|
|
12
|
+
* where a spin is [stop0…stopN, winBp, lineMask, scatterCount].
|
|
13
|
+
*/
|
|
14
|
+
export function slot(cfg) {
|
|
15
|
+
const { reels, freeReels = reels, rows, lines, pays, wild, scatter, maxWinBp } = cfg;
|
|
16
|
+
const n = reels.length;
|
|
17
|
+
|
|
18
|
+
const grid = (strips, stops) => stops.map((s, r) => Array.from({ length: rows }, (_, y) => strips[r][(s + y) % strips[r].length]));
|
|
19
|
+
|
|
20
|
+
// One spin's wins: each line pays its best run from the left (wilds stand in).
|
|
21
|
+
function evaluate(strips, stops) {
|
|
22
|
+
const g = grid(strips, stops);
|
|
23
|
+
let win = 0, mask = 0;
|
|
24
|
+
lines.forEach((line, li) => {
|
|
25
|
+
const cells = line.map((y, r) => g[r][y]);
|
|
26
|
+
const sym = cells.find((c) => c !== wild) ?? wild;
|
|
27
|
+
if (sym === scatter.symbol) return;
|
|
28
|
+
let count = 0;
|
|
29
|
+
while (count < n && (cells[count] === sym || cells[count] === wild)) count++;
|
|
30
|
+
const pay = (pays[sym] || {})[count] || 0;
|
|
31
|
+
// A run of wilds alone pays as wilds when that is more.
|
|
32
|
+
let wc = 0;
|
|
33
|
+
while (wc < n && cells[wc] === wild) wc++;
|
|
34
|
+
const best = Math.max(pay, (pays[wild] || {})[wc] || 0);
|
|
35
|
+
if (best > 0) { win += best; mask |= 1 << li; }
|
|
36
|
+
});
|
|
37
|
+
const scatters = g.flat().filter((c) => c === scatter.symbol).length;
|
|
38
|
+
win += (scatter.pays[scatters] || 0);
|
|
39
|
+
return { win, mask, scatters };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const spin = (strips, next) => strips.map((s) => next.int(s.length));
|
|
43
|
+
|
|
44
|
+
/** One whole round: the row's data and what it pays (bp of the stake).
|
|
45
|
+
* A bought round is the free spins alone, as three scatters start them. */
|
|
46
|
+
function round(next, buy = false) {
|
|
47
|
+
const stops = buy ? null : spin(reels, next);
|
|
48
|
+
const base = buy ? { win: 0, scatters: 3 } : evaluate(reels, stops);
|
|
49
|
+
const data = buy ? { buy: 1 } : { b: [...stops, base.win, base.mask, base.scatters] };
|
|
50
|
+
let total = base.win;
|
|
51
|
+
let left = scatter.spins[base.scatters] || 0;
|
|
52
|
+
if (left) {
|
|
53
|
+
data.f = [];
|
|
54
|
+
data.x = scatter.multiplier;
|
|
55
|
+
while (left > 0 && data.f.length < scatter.cap) {
|
|
56
|
+
left--;
|
|
57
|
+
const s = spin(freeReels, next);
|
|
58
|
+
const e = evaluate(freeReels, s);
|
|
59
|
+
data.f.push([...s, e.win * scatter.multiplier, e.mask, e.scatters]);
|
|
60
|
+
total += e.win * scatter.multiplier;
|
|
61
|
+
left += scatter.retrigger[e.scatters] || 0;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (total > maxWinBp) { total = maxWinBp; data.cap = 1; }
|
|
65
|
+
return { data, pay: total };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { evaluate, round, grid };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Plays `count` rounds (bought ones with `buy`) and keeps them as rows: every paying round, and up to
|
|
73
|
+
* `losing` different losing rounds that share the weight of every losing
|
|
74
|
+
* round played (so the rows keep the played RTP and hit rate before fit).
|
|
75
|
+
*/
|
|
76
|
+
export function books(game, next, count, losing = 4000, buy = false) {
|
|
77
|
+
const wins = new Map(), lose = new Map();
|
|
78
|
+
let lost = 0n;
|
|
79
|
+
for (let i = 0; i < count; i++) {
|
|
80
|
+
const { data, pay } = game.round(next, buy);
|
|
81
|
+
if (pay <= 0) {
|
|
82
|
+
lost++;
|
|
83
|
+
if (lose.size < losing) lose.set(JSON.stringify(data), { weight: 0n, data, pay: { spin: 0 } });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const key = JSON.stringify(data);
|
|
87
|
+
const r = wins.get(key);
|
|
88
|
+
if (r) r.weight += 1n;
|
|
89
|
+
else wins.set(key, { weight: 1n, data, pay: { spin: pay } });
|
|
90
|
+
}
|
|
91
|
+
const zeros = [...lose.values()];
|
|
92
|
+
zeros.forEach((r, i) => { r.weight = lost / BigInt(zeros.length) + (BigInt(i) < lost % BigInt(zeros.length) ? 1n : 0n); });
|
|
93
|
+
return [...zeros.filter((r) => r.weight > 0n), ...wins.values()];
|
|
94
|
+
}
|