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,55 @@
|
|
|
1
|
+
// What a screenshot shows: whether the frame is one flat colour. A game that
|
|
2
|
+
// draws on a canvas the run's browser could not draw (WebGL without a GPU), or
|
|
3
|
+
// that never drew, passes the size-based `draws` check and still shows the
|
|
4
|
+
// reviewer and the player a black frame (Pump Junkie v1, 26 Oct).
|
|
5
|
+
import { inflateSync } from 'node:zlib';
|
|
6
|
+
|
|
7
|
+
/** Decodes an 8-bit, non-interlaced PNG (what Chrome's screenshots are) into { width, height, channels, pixels }. */
|
|
8
|
+
export function decodePNG(buf) {
|
|
9
|
+
if (buf.readUInt32BE(0) !== 0x89504e47) throw new Error('not a PNG');
|
|
10
|
+
let off = 8, width = 0, height = 0, depth = 0, type = 0, interlace = 0;
|
|
11
|
+
const idat = [];
|
|
12
|
+
while (off < buf.length) {
|
|
13
|
+
const len = buf.readUInt32BE(off), kind = buf.toString('latin1', off + 4, off + 8), data = buf.subarray(off + 8, off + 8 + len);
|
|
14
|
+
if (kind === 'IHDR') { width = data.readUInt32BE(0); height = data.readUInt32BE(4); depth = data[8]; type = data[9]; interlace = data[12]; }
|
|
15
|
+
else if (kind === 'IDAT') idat.push(data);
|
|
16
|
+
else if (kind === 'IEND') break;
|
|
17
|
+
off += 12 + len;
|
|
18
|
+
}
|
|
19
|
+
const channels = { 2: 3, 6: 4, 0: 1, 4: 2 }[type];
|
|
20
|
+
if (depth !== 8 || !channels || interlace) throw new Error(`unsupported PNG (depth ${depth}, type ${type}, interlace ${interlace})`);
|
|
21
|
+
const raw = inflateSync(Buffer.concat(idat)), stride = width * channels, pixels = Buffer.alloc(stride * height);
|
|
22
|
+
for (let y = 0; y < height; y++) {
|
|
23
|
+
const f = raw[y * (stride + 1)], src = raw.subarray(y * (stride + 1) + 1, (y + 1) * (stride + 1)), row = pixels.subarray(y * stride, (y + 1) * stride), up = y ? pixels.subarray((y - 1) * stride, y * stride) : null;
|
|
24
|
+
for (let x = 0; x < stride; x++) {
|
|
25
|
+
const a = x >= channels ? row[x - channels] : 0, b = up ? up[x] : 0, c = up && x >= channels ? up[x - channels] : 0;
|
|
26
|
+
let v = src[x];
|
|
27
|
+
if (f === 1) v += a;
|
|
28
|
+
else if (f === 2) v += b;
|
|
29
|
+
else if (f === 3) v += (a + b) >> 1;
|
|
30
|
+
else if (f === 4) { const p = a + b - c, pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c); v += pa <= pb && pa <= pc ? a : pb <= pc ? b : c; }
|
|
31
|
+
row[x] = v & 255;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { width, height, channels, pixels };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The share of the picture in its most common colour (sampled on a grid, colours
|
|
39
|
+
* within `tol` of each other counted as one): 1 is a flat frame.
|
|
40
|
+
*/
|
|
41
|
+
export function flatShare(png, { step = 8, tol = 6 } = {}) {
|
|
42
|
+
const { width, height, channels, pixels } = decodePNG(png);
|
|
43
|
+
const counts = new Map();
|
|
44
|
+
let n = 0;
|
|
45
|
+
for (let y = 0; y < height; y += step) {
|
|
46
|
+
for (let x = 0; x < width; x += step) {
|
|
47
|
+
const i = (y * width + x) * channels;
|
|
48
|
+
const g = channels >= 3 ? [pixels[i], pixels[i + 1], pixels[i + 2]] : [pixels[i], pixels[i], pixels[i]];
|
|
49
|
+
const key = g.map((v) => Math.round(v / tol)).join(',');
|
|
50
|
+
counts.set(key, (counts.get(key) || 0) + 1);
|
|
51
|
+
n++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return n ? Math.max(...counts.values()) / n : 1;
|
|
55
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
package conformance
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"errors"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"log/slog"
|
|
9
|
+
"mime"
|
|
10
|
+
"net"
|
|
11
|
+
"net/http"
|
|
12
|
+
"net/url"
|
|
13
|
+
"regexp"
|
|
14
|
+
"slices"
|
|
15
|
+
"strings"
|
|
16
|
+
"sync"
|
|
17
|
+
"time"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// Reports is the endpoint a policy's report-uri and report-to name: browsers
|
|
21
|
+
// POST what the policy refused in a player's page, and each violation becomes
|
|
22
|
+
// one log line, msg "csp violation", which the alert on the service mails to
|
|
23
|
+
// staff. Nothing is stored and nothing is answered but 204. A report is the
|
|
24
|
+
// browser's word, sent after the fact, and a browser extension or a forged
|
|
25
|
+
// POST can send one as well as a game can earn one: it is a reason for staff
|
|
26
|
+
// to look at a game and kill it, never a kill by itself.
|
|
27
|
+
//
|
|
28
|
+
// What reaches the logs is bounded in content and in volume. In content: the
|
|
29
|
+
// page the report names (the caller's Locate), the directive, the blocked
|
|
30
|
+
// URI's scheme and host, and a count; never any other text from the report.
|
|
31
|
+
// In volume: lines, not requests, are what the allowances count, and only a
|
|
32
|
+
// line that is logged spends one. A report that is dropped unheard costs its
|
|
33
|
+
// sender nothing, so a game posting junk to its own report endpoint (its
|
|
34
|
+
// origin, which connect-src allows) cannot spend its player's allowance and
|
|
35
|
+
// silence the browser's own reports about it; and the allowance is one
|
|
36
|
+
// address's about one page, so a flood about one page never starves another's.
|
|
37
|
+
// When an allowance is spent, one line, "csp violation flood", names the page,
|
|
38
|
+
// so the throttling is itself seen.
|
|
39
|
+
type Reports struct {
|
|
40
|
+
// Locate names the page a report is about from its document URL, already
|
|
41
|
+
// known to be on the host the report reached: the attributes to log, or
|
|
42
|
+
// false for a page that is not ours, whose report is dropped unheard.
|
|
43
|
+
Locate func(doc *url.URL) ([]slog.Attr, bool)
|
|
44
|
+
// Hops: the X-Forwarded-For entry that many from the right names the client
|
|
45
|
+
// (1 on Cloud Run, 2 behind Google's load balancer, 3 with Cloudflare before
|
|
46
|
+
// it); 0 is the connection's own address, which behind a proxy throttles
|
|
47
|
+
// every player as one.
|
|
48
|
+
Hops int
|
|
49
|
+
Log *slog.Logger // slog.Default() when nil
|
|
50
|
+
Now func() time.Time // time.Now when nil
|
|
51
|
+
|
|
52
|
+
mu sync.Mutex
|
|
53
|
+
buckets map[string]*bucket // one address about one page
|
|
54
|
+
all bucket
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const (
|
|
58
|
+
maxReport = 8 << 10 // a report is a few hundred bytes; a batch a few of them
|
|
59
|
+
// Lines one address may add to the logs about one page: ten at once, then
|
|
60
|
+
// ten a minute, plus the one that says the rest were dropped. Lines the
|
|
61
|
+
// instance may add in all: three hundred at once, then three hundred a
|
|
62
|
+
// minute. A request that logs nothing spends nothing.
|
|
63
|
+
pairBurst, pairPerSecond = 10, 10.0 / 60
|
|
64
|
+
allBurst, allPerSecond = 300, 300.0 / 60
|
|
65
|
+
maxPairs = 4096
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
type bucket struct {
|
|
69
|
+
tokens float64
|
|
70
|
+
at time.Time // last touched
|
|
71
|
+
warned bool // the flood line for this exhaustion has been logged
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func (b *bucket) refill(now time.Time, burst, perSecond float64) {
|
|
75
|
+
if b.at.IsZero() {
|
|
76
|
+
b.tokens = burst
|
|
77
|
+
} else {
|
|
78
|
+
b.tokens = min(burst, b.tokens+now.Sub(b.at).Seconds()*perSecond)
|
|
79
|
+
}
|
|
80
|
+
b.at = now
|
|
81
|
+
// Quiet long enough to be whole again: the next flood is a new one.
|
|
82
|
+
if b.tokens >= burst {
|
|
83
|
+
b.warned = false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type verdict int
|
|
88
|
+
|
|
89
|
+
const (
|
|
90
|
+
logged verdict = iota // the line may be logged
|
|
91
|
+
flooded // the pair's allowance is spent: one line says so
|
|
92
|
+
silenced // nothing: the pair has been told, or the instance is spent
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
func (rp *Reports) now() time.Time {
|
|
96
|
+
if rp.Now != nil {
|
|
97
|
+
return rp.Now()
|
|
98
|
+
}
|
|
99
|
+
return time.Now()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func (rp *Reports) log() *slog.Logger {
|
|
103
|
+
if rp.Log != nil {
|
|
104
|
+
return rp.Log
|
|
105
|
+
}
|
|
106
|
+
return slog.Default()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// allow spends one line of the pair's allowance (one address about one page)
|
|
110
|
+
// and one of the instance's, for a line that is about to be logged. The pair
|
|
111
|
+
// map never grows past maxPairs: a pair is remembered only when the instance
|
|
112
|
+
// has a line to give, so at most 600 pairs are touched in any minute, and
|
|
113
|
+
// when the map is full every pair idle long enough to be whole again is
|
|
114
|
+
// forgotten, which always leaves room; should it not, the new pair is refused
|
|
115
|
+
// rather than remembered.
|
|
116
|
+
func (rp *Reports) allow(pair string, now time.Time) verdict {
|
|
117
|
+
rp.mu.Lock()
|
|
118
|
+
defer rp.mu.Unlock()
|
|
119
|
+
rp.all.refill(now, allBurst, allPerSecond)
|
|
120
|
+
if rp.all.tokens < 1 {
|
|
121
|
+
return silenced
|
|
122
|
+
}
|
|
123
|
+
b := rp.buckets[pair]
|
|
124
|
+
if b == nil {
|
|
125
|
+
if rp.buckets == nil {
|
|
126
|
+
rp.buckets = map[string]*bucket{}
|
|
127
|
+
}
|
|
128
|
+
if len(rp.buckets) >= maxPairs {
|
|
129
|
+
for k, v := range rp.buckets {
|
|
130
|
+
if now.Sub(v.at).Seconds()*pairPerSecond >= pairBurst {
|
|
131
|
+
delete(rp.buckets, k)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if len(rp.buckets) >= maxPairs {
|
|
135
|
+
return silenced
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
b = &bucket{}
|
|
139
|
+
rp.buckets[pair] = b
|
|
140
|
+
}
|
|
141
|
+
b.refill(now, pairBurst, pairPerSecond)
|
|
142
|
+
if b.tokens >= 1 {
|
|
143
|
+
b.tokens--
|
|
144
|
+
rp.all.tokens--
|
|
145
|
+
return logged
|
|
146
|
+
}
|
|
147
|
+
if b.warned {
|
|
148
|
+
return silenced
|
|
149
|
+
}
|
|
150
|
+
b.warned = true
|
|
151
|
+
rp.all.tokens--
|
|
152
|
+
return flooded
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
func (rp *Reports) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
156
|
+
h := w.Header()
|
|
157
|
+
h.Set("X-Content-Type-Options", "nosniff")
|
|
158
|
+
h.Set("Cache-Control", "no-store")
|
|
159
|
+
if r.Method != http.MethodPost {
|
|
160
|
+
h.Set("Allow", "POST")
|
|
161
|
+
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
ct, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
|
165
|
+
if ct != "application/csp-report" && ct != "application/reports+json" {
|
|
166
|
+
http.Error(w, "a report is application/csp-report or application/reports+json", http.StatusUnsupportedMediaType)
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
if r.ContentLength > maxReport {
|
|
170
|
+
http.Error(w, "report too large", http.StatusRequestEntityTooLarge)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxReport))
|
|
174
|
+
var tooLarge *http.MaxBytesError
|
|
175
|
+
if errors.As(err, &tooLarge) {
|
|
176
|
+
http.Error(w, "report too large", http.StatusRequestEntityTooLarge)
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
if err != nil {
|
|
180
|
+
http.Error(w, "unreadable report", http.StatusBadRequest)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
// Read and located first, charged only for what is logged: a report that
|
|
184
|
+
// is nobody's costs nobody's allowance.
|
|
185
|
+
from, now := rp.client(r), rp.now()
|
|
186
|
+
kept, refused := 0, 0
|
|
187
|
+
for _, l := range rp.lines(r.Host, parse(ct, body)) {
|
|
188
|
+
switch rp.allow(from+" "+l.pageKey, now) {
|
|
189
|
+
case logged:
|
|
190
|
+
rp.log().LogAttrs(r.Context(), slog.LevelWarn, "csp violation", l.attrs()...)
|
|
191
|
+
kept++
|
|
192
|
+
case flooded:
|
|
193
|
+
rp.log().LogAttrs(r.Context(), slog.LevelWarn, "csp violation flood", l.page...)
|
|
194
|
+
refused++
|
|
195
|
+
default:
|
|
196
|
+
refused++
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if kept == 0 && refused > 0 {
|
|
200
|
+
http.Error(w, "too many reports", http.StatusTooManyRequests)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
w.WriteHeader(http.StatusNoContent)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
func (rp *Reports) client(r *http.Request) string { return ClientAddress(r, rp.Hops) }
|
|
207
|
+
|
|
208
|
+
// ClientAddress is the client's address, as the studio reads it: the
|
|
209
|
+
// connection's, or behind hops trusted proxies the X-Forwarded-For entry that
|
|
210
|
+
// many from the right, never one the client wrote. The sandbox shell's proxy
|
|
211
|
+
// names the player to its round server with it (shell.Sandbox).
|
|
212
|
+
func ClientAddress(r *http.Request, hops int) string {
|
|
213
|
+
if f := strings.Split(strings.Join(r.Header.Values("X-Forwarded-For"), ","), ","); hops > 0 && len(f) >= hops && f[0] != "" {
|
|
214
|
+
if ip := net.ParseIP(strings.TrimSpace(f[len(f)-hops])); ip != nil {
|
|
215
|
+
return ip.String()
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if h, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
|
219
|
+
return h
|
|
220
|
+
}
|
|
221
|
+
return r.RemoteAddr
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
type violation struct{ document, directive, blocked string }
|
|
225
|
+
|
|
226
|
+
// parse reads a report-uri report ({"csp-report": {...}}) or a Reporting API
|
|
227
|
+
// batch ([{type, body}, ...], csp-violation entries only). What does not parse
|
|
228
|
+
// is nothing.
|
|
229
|
+
func parse(ct string, body []byte) []violation {
|
|
230
|
+
if ct == "application/csp-report" {
|
|
231
|
+
var legacy struct {
|
|
232
|
+
Report struct {
|
|
233
|
+
Document string `json:"document-uri"`
|
|
234
|
+
Effective string `json:"effective-directive"`
|
|
235
|
+
Violated string `json:"violated-directive"`
|
|
236
|
+
Blocked string `json:"blocked-uri"`
|
|
237
|
+
} `json:"csp-report"`
|
|
238
|
+
}
|
|
239
|
+
if json.Unmarshal(body, &legacy) != nil {
|
|
240
|
+
return nil
|
|
241
|
+
}
|
|
242
|
+
d := legacy.Report.Effective
|
|
243
|
+
if d == "" {
|
|
244
|
+
d = legacy.Report.Violated
|
|
245
|
+
}
|
|
246
|
+
return []violation{{legacy.Report.Document, d, legacy.Report.Blocked}}
|
|
247
|
+
}
|
|
248
|
+
var batch []struct {
|
|
249
|
+
Type string `json:"type"`
|
|
250
|
+
Body struct {
|
|
251
|
+
Document string `json:"documentURL"`
|
|
252
|
+
Effective string `json:"effectiveDirective"`
|
|
253
|
+
Blocked string `json:"blockedURL"`
|
|
254
|
+
} `json:"body"`
|
|
255
|
+
}
|
|
256
|
+
if json.Unmarshal(body, &batch) != nil {
|
|
257
|
+
return nil
|
|
258
|
+
}
|
|
259
|
+
var out []violation
|
|
260
|
+
for _, rep := range batch {
|
|
261
|
+
if rep.Type == "csp-violation" {
|
|
262
|
+
out = append(out, violation{rep.Body.Document, rep.Body.Effective, rep.Body.Blocked})
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return out
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// A line to log: the page (Locate's attributes, and their text as the key an
|
|
269
|
+
// allowance is kept under), what was refused, and how often the batch held it.
|
|
270
|
+
type line struct {
|
|
271
|
+
page []slog.Attr
|
|
272
|
+
pageKey string
|
|
273
|
+
directive, blocked string
|
|
274
|
+
count int
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
func (l *line) attrs() []slog.Attr {
|
|
278
|
+
return slices.Concat(l.page, []slog.Attr{slog.String("directive", l.directive), slog.String("blocked", l.blocked), slog.Int("count", l.count)})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// lines keeps the violations of pages on host that Locate knows, one line per
|
|
282
|
+
// distinct (page, directive, blocked) with how many times the batch held it.
|
|
283
|
+
func (rp *Reports) lines(host string, vs []violation) []*line {
|
|
284
|
+
var out []*line
|
|
285
|
+
seen := map[string]*line{}
|
|
286
|
+
for _, v := range vs {
|
|
287
|
+
doc, err := url.Parse(v.document)
|
|
288
|
+
if err != nil || !strings.EqualFold(doc.Host, host) {
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
291
|
+
page, ok := rp.Locate(doc)
|
|
292
|
+
if !ok {
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
l := &line{page: page, pageKey: fmt.Sprint(page), directive: directive(v.directive), blocked: blocked(v.blocked), count: 1}
|
|
296
|
+
key := l.pageKey + " " + l.directive + " " + l.blocked
|
|
297
|
+
if same := seen[key]; same != nil {
|
|
298
|
+
same.count++
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
301
|
+
seen[key] = l
|
|
302
|
+
out = append(out, l)
|
|
303
|
+
}
|
|
304
|
+
return out
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
var (
|
|
308
|
+
directiveRE = regexp.MustCompile(`^[a-z][a-z-]{0,31}$`)
|
|
309
|
+
schemeRE = regexp.MustCompile(`^[a-z][a-z0-9+.-]{0,15}$`)
|
|
310
|
+
// A host name or a bracketed IPv6 address, with a port or not. Go's URL
|
|
311
|
+
// parser lets much more through as a host (quotes, brackets, semicolons:
|
|
312
|
+
// text), and none of that is a host a browser fetched from.
|
|
313
|
+
hostRE = regexp.MustCompile(`^([a-z0-9.-]+|\[[0-9a-f:.]+\])(:[0-9]{1,5})?$`)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
// A directive name; an old browser's violated-directive carries the whole
|
|
317
|
+
// directive, whose first word is the name. Anything else is "other".
|
|
318
|
+
func directive(s string) string {
|
|
319
|
+
s, _, _ = strings.Cut(s, " ")
|
|
320
|
+
if directiveRE.MatchString(s) {
|
|
321
|
+
return s
|
|
322
|
+
}
|
|
323
|
+
return "other"
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// The blocked URI's scheme and host, or one of the browsers' own words for
|
|
327
|
+
// what has no host (inline, eval, data...): never a path, a query, or any text
|
|
328
|
+
// a forged report could put in a log line.
|
|
329
|
+
func blocked(s string) string {
|
|
330
|
+
switch s {
|
|
331
|
+
case "":
|
|
332
|
+
return "none"
|
|
333
|
+
case "inline", "eval", "wasm-eval", "self", "data", "blob", "about", "filesystem":
|
|
334
|
+
return s
|
|
335
|
+
}
|
|
336
|
+
u, err := url.Parse(s)
|
|
337
|
+
if err != nil || !schemeRE.MatchString(u.Scheme) {
|
|
338
|
+
return "other"
|
|
339
|
+
}
|
|
340
|
+
host := strings.ToLower(u.Host)
|
|
341
|
+
if !hostRE.MatchString(host) {
|
|
342
|
+
return "other"
|
|
343
|
+
}
|
|
344
|
+
host = u.Scheme + "://" + host
|
|
345
|
+
if len(host) > 120 {
|
|
346
|
+
host = host[:120]
|
|
347
|
+
}
|
|
348
|
+
return host
|
|
349
|
+
}
|