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,208 @@
|
|
|
1
|
+
// The code analysis of a whole bundle, for the worker thread (analyse-worker)
|
|
2
|
+
// and for anything that wants it in-process (tests, a trace): every script
|
|
3
|
+
// and page parsed and scoped first, the files then wired to one another — a
|
|
4
|
+
// module's imports to the exports of the file they name, a classic script's
|
|
5
|
+
// globals to every other script's, a page's elements to the scripts it loads
|
|
6
|
+
// (and to the modules they import), every write to a property or an attribute
|
|
7
|
+
// to a pool the reads consult, every call to the function it reaches in
|
|
8
|
+
// whatever file — and each file judged with all of that in reach, so a value
|
|
9
|
+
// put into the page is followed to where it is written.
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import { analyseData, analyseHtml, analyseXml, judgeSource, prepareSource, resolveSpecifier, scriptKind, RULES } from './analyse.mjs';
|
|
12
|
+
|
|
13
|
+
// Every <script …> tag of a page, its attributes read for src and type (scriptKind).
|
|
14
|
+
const SCRIPT_TAG = /<script\b([^>]*)>/gi;
|
|
15
|
+
const SRC_ATTR = /\ssrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
|
|
16
|
+
const TYPE_ATTR = /\stype\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
|
|
17
|
+
// The scripts a page loads by src: [{ target, kind }], kind 'script' (a classic
|
|
18
|
+
// script: its top-level declarations are the page's globals), 'module', or
|
|
19
|
+
// null for a data block the browser never runs.
|
|
20
|
+
function scriptLoads(path, text) {
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const m of text.matchAll(SCRIPT_TAG)) {
|
|
23
|
+
const attrs = m[1];
|
|
24
|
+
const src = SRC_ATTR.exec(attrs);
|
|
25
|
+
if (!src) continue;
|
|
26
|
+
const target = resolveSpecifier(path, (src[1] ?? src[2] ?? src[3] ?? '').trim());
|
|
27
|
+
if (target == null) continue;
|
|
28
|
+
const type = TYPE_ATTR.exec(attrs);
|
|
29
|
+
const kind = scriptKind(type ? (type[1] ?? type[2] ?? type[3] ?? '') : '');
|
|
30
|
+
if (kind === 'importmap') continue;
|
|
31
|
+
out.push({ target, kind });
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* `files`: [{ path, abs, kind }] with kind 'xml', 'html', 'data' or 'script'
|
|
38
|
+
* (scan.mjs decides). Returns one result per file: { path, findings, units,
|
|
39
|
+
* wasm, sourceMappingURL }. `hook(unit)` is called with each prepared unit
|
|
40
|
+
* before judging (a trace instruments the analysis there).
|
|
41
|
+
*/
|
|
42
|
+
export function analyseBundle(files, { read = (abs) => readFileSync(abs, 'utf8'), hook = null } = {}) {
|
|
43
|
+
const out = [];
|
|
44
|
+
const units = []; // { path, unit, page, result, inline }
|
|
45
|
+
const pages = new Map(); // path → { path, elements, overflow, loads: [paths], units }
|
|
46
|
+
const modules = new Map(); // path → unit (a .js/.mjs file), for imports
|
|
47
|
+
const jsons = new Map(); // path → parsed JSON (or undefined), for JSON imports
|
|
48
|
+
const texts = new Map(); // path → text of a .json data file
|
|
49
|
+
const fail = (result, e) => result.findings.push({ level: 'fail', rule: 'analyser', line: 1, what: `the analyser stopped on this file: ${e.message}` });
|
|
50
|
+
|
|
51
|
+
// The files every page loads by a plain <script src> (no type, or a
|
|
52
|
+
// JavaScript MIME type) are classic scripts: parsed as the browser parses
|
|
53
|
+
// them (a module-only construct is a syntax error there, as in the
|
|
54
|
+
// browser), with their top-level declarations the page's globals, which
|
|
55
|
+
// every other script reads. A file loaded both ways is read as a classic
|
|
56
|
+
// script (the over-approximation: more is global, nothing less).
|
|
57
|
+
const classic = new Set();
|
|
58
|
+
const texts0 = new Map();
|
|
59
|
+
for (const { path, abs, kind } of files) {
|
|
60
|
+
if (kind !== 'html') continue;
|
|
61
|
+
let text;
|
|
62
|
+
try { text = read(abs); } catch { continue; }
|
|
63
|
+
texts0.set(abs, text);
|
|
64
|
+
for (const { target, kind: k } of scriptLoads(path, text)) if (k === 'script') classic.add(target);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const { path, abs, kind } of files) {
|
|
68
|
+
const result = { path, findings: [], units: [], wasm: [], sourceMappingURL: null };
|
|
69
|
+
out.push(result);
|
|
70
|
+
try {
|
|
71
|
+
const text = texts0.has(abs) ? texts0.get(abs) : read(abs);
|
|
72
|
+
if (kind === 'xml') {
|
|
73
|
+
// An SVG or XML document runs its scripts and handler attributes
|
|
74
|
+
// when framed or embedded, not when drawn: read as the browser
|
|
75
|
+
// renders it (prefixes dropped; a DTD subset refused), any
|
|
76
|
+
// <script> refused outright (a game's scripts are .js files).
|
|
77
|
+
result.findings.push(...analyseXml(text).findings);
|
|
78
|
+
} else if (kind === 'data') {
|
|
79
|
+
// A text file that is not code or markup (.json, .txt, .csv, a
|
|
80
|
+
// template of any extension): markup the game may put into the
|
|
81
|
+
// page is walked as code's strings are. A .json the analyser
|
|
82
|
+
// cannot parse is refused: the page would read it as JSON, and
|
|
83
|
+
// what the analyser cannot read the same way it does not pass.
|
|
84
|
+
const r = analyseData(text);
|
|
85
|
+
result.findings.push(...r.findings);
|
|
86
|
+
if (/\.json$/i.test(path) && !r.parsed) result.findings.push({ level: RULES['data-file'].level, rule: 'data-file', line: 1, what: 'not valid JSON' });
|
|
87
|
+
if (/\.json$/i.test(path) && r.parsed) texts.set(path, text);
|
|
88
|
+
} else if (kind === 'html') {
|
|
89
|
+
const page = analyseHtml(text);
|
|
90
|
+
result.findings.push(...page.findings);
|
|
91
|
+
const info = { path, elements: page.elements, overflow: page.overflow, loads: [], units: [] };
|
|
92
|
+
pages.set(path, info);
|
|
93
|
+
for (const script of page.scripts) {
|
|
94
|
+
const unit = prepareSource(script.code, { mode: script.mode, lineOffset: script.line - 1 });
|
|
95
|
+
unit.path = path;
|
|
96
|
+
units.push({ path, unit, page: info, result, inline: true });
|
|
97
|
+
info.units.push(unit);
|
|
98
|
+
}
|
|
99
|
+
// The scripts the page loads by src (a <script src> has no body for the walk to return).
|
|
100
|
+
for (const { target } of scriptLoads(path, text)) if (!info.loads.includes(target)) info.loads.push(target);
|
|
101
|
+
} else {
|
|
102
|
+
const unit = prepareSource(text, { mode: classic.has(path) ? 'script' : 'auto' });
|
|
103
|
+
unit.path = path;
|
|
104
|
+
units.push({ path, unit, page: null, result, inline: false });
|
|
105
|
+
modules.set(path, unit);
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
// A shape of syntax the analyser did not expect fails the file, never passes it.
|
|
109
|
+
fail(result, e);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---- wiring -----------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
// A JSON module: its parsed value, read once.
|
|
116
|
+
const jsonOf = (path) => {
|
|
117
|
+
if (!jsons.has(path)) { const t = texts.get(path); let v; try { v = t == null ? undefined : JSON.parse(t.charCodeAt(0) === 0xfeff ? t.slice(1) : t); } catch { v = undefined; } jsons.set(path, v); }
|
|
118
|
+
return jsons.get(path);
|
|
119
|
+
};
|
|
120
|
+
// What a module specifier names, from a file: the module (its analysis), a JSON file ({ json }), or null.
|
|
121
|
+
const moduleFrom = (fromPath) => (specifier) => {
|
|
122
|
+
const target = resolveSpecifier(fromPath, specifier);
|
|
123
|
+
if (target == null) return null;
|
|
124
|
+
const m = modules.get(target);
|
|
125
|
+
if (m) return m.ast ? { analysis: m.analysis } : null;
|
|
126
|
+
if (/\.json$/i.test(target)) { const v = jsonOf(target); return v === undefined ? null : { json: v }; }
|
|
127
|
+
return null;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// The pool: every classic script's root bindings and every module's globals
|
|
131
|
+
// (window.X = …) by name; every write by its key; every element the code
|
|
132
|
+
// writes as markup.
|
|
133
|
+
const pool = { globals: new Map(), writes: new Map(), written: [], writtenOverflow: false };
|
|
134
|
+
for (const { unit } of units) {
|
|
135
|
+
const a = unit.analysis;
|
|
136
|
+
if (!unit.ast) continue;
|
|
137
|
+
for (const [name, binding] of a.root.names) {
|
|
138
|
+
if (a.mode !== 'script' && !binding.global) continue;
|
|
139
|
+
if (!binding.alias && !binding.hidden) continue;
|
|
140
|
+
if (!pool.globals.has(name)) pool.globals.set(name, []);
|
|
141
|
+
pool.globals.get(name).push({ binding, scope: a.root });
|
|
142
|
+
}
|
|
143
|
+
for (const w of a.writes) {
|
|
144
|
+
if (!a.targetOf(w)) continue;
|
|
145
|
+
const key = w.key ?? '*';
|
|
146
|
+
if (!pool.writes.has(key)) pool.writes.set(key, []);
|
|
147
|
+
pool.writes.get(key).push(w);
|
|
148
|
+
}
|
|
149
|
+
if (a.writtenOverflow) pool.writtenOverflow = true;
|
|
150
|
+
else pool.written.push(...a.written);
|
|
151
|
+
}
|
|
152
|
+
if (pool.written.length > 100000) { pool.written = []; pool.writtenOverflow = true; }
|
|
153
|
+
|
|
154
|
+
// The pages a script runs in: its own for an inline script; those whose
|
|
155
|
+
// <script src> names it for a file; those of its importers for a module
|
|
156
|
+
// imported by another (to a fixpoint, since imports chain).
|
|
157
|
+
const pagesOf = new Map();
|
|
158
|
+
for (const u of units) pagesOf.set(u.unit, new Set(u.page ? [u.page] : []));
|
|
159
|
+
for (const info of pages.values()) for (const target of info.loads) { const m = modules.get(target); if (m) pagesOf.get(m)?.add(info); }
|
|
160
|
+
for (let changed = true, rounds = 0; changed && rounds < 50; rounds++) {
|
|
161
|
+
changed = false;
|
|
162
|
+
for (const { path, unit } of units) {
|
|
163
|
+
if (!unit.ast) continue;
|
|
164
|
+
const mine = pagesOf.get(unit);
|
|
165
|
+
for (const specifier of unit.analysis.imports) {
|
|
166
|
+
const target = resolveSpecifier(path, specifier);
|
|
167
|
+
const m = target != null ? modules.get(target) : null;
|
|
168
|
+
if (!m) continue;
|
|
169
|
+
const theirs = pagesOf.get(m);
|
|
170
|
+
for (const p of mine) if (!theirs.has(p)) { theirs.add(p); changed = true; }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
for (const { path, unit } of units) {
|
|
175
|
+
const a = unit.analysis;
|
|
176
|
+
a.modules = moduleFrom(path);
|
|
177
|
+
a.pool = pool;
|
|
178
|
+
a.pages = [...(pagesOf.get(unit) ?? [])];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Every reference, in every file, to a function of another file (or a method
|
|
182
|
+
// of a written object) otherwise than as a callee — handed to forEach, a
|
|
183
|
+
// timer, a listener, kept in a name — escapes it: called from where the
|
|
184
|
+
// analyser does not see, its parameters stay unknown. Then every call, in
|
|
185
|
+
// every file, to a function the resolver reaches (a method of an object
|
|
186
|
+
// another file set on the global object, an export called by its importer,
|
|
187
|
+
// a method of a written object): a call site of that function, so its
|
|
188
|
+
// parameters are the arguments written there.
|
|
189
|
+
for (const { unit } of units) if (unit.ast) { unit.analysis.cache.clear(); unit.analysis.mentioned.clear(); unit.analysis.globalNames.clear(); unit.analysis.writesByKey = null; unit.analysis.writesBySyntax = null; } // (pass 1 resolved before the wiring)
|
|
190
|
+
for (const { unit } of units) if (unit.ast) unit.analysis.collectEscapes();
|
|
191
|
+
for (const { unit } of units) if (unit.ast) unit.analysis.collectSites();
|
|
192
|
+
|
|
193
|
+
// ---- judging -----------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
for (const { unit, result, inline } of units) {
|
|
196
|
+
try {
|
|
197
|
+
if (hook) hook(unit);
|
|
198
|
+
const r = judgeSource(unit);
|
|
199
|
+
result.findings.push(...r.findings);
|
|
200
|
+
result.wasm.push(...r.wasm);
|
|
201
|
+
if (inline) result.units.push({ kind: 'inline', line: unit.analysis.lineOffset + 1, minified: r.minified, names: r.names, short: r.short });
|
|
202
|
+
else { result.sourceMappingURL = r.sourceMappingURL; result.units.push({ kind: 'file', line: 1, minified: r.minified, names: r.names, short: r.short }); }
|
|
203
|
+
} catch (e) {
|
|
204
|
+
fail(result, e);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fake clock a time-travel page runs under, and how the run drives it.
|
|
3
|
+
*
|
|
4
|
+
* The implementation is Playwright's own (the clock its context.clock
|
|
5
|
+
* installs: fake Date, timers, animation frames, idle callbacks, performance
|
|
6
|
+
* and Intl, flowing at real pace and jumped by runFor/fastForward), read from
|
|
7
|
+
* the installed playwright-core. Playwright installs it under a global the
|
|
8
|
+
* game could find (`globalThis.__pwClock`) and drives it by evaluating against
|
|
9
|
+
* that name in every frame; the run installs the same code from its own init
|
|
10
|
+
* script with no global at all, and drives it through the message channel
|
|
11
|
+
* travel.mjs clockTrap listens on under the run's token: the site page (the
|
|
12
|
+
* run's own) posts `token:clock:<op>:<id>:<ms>` into every frame and waits for
|
|
13
|
+
* each frame's `token:clockdone:<id>:<now>`. Nothing of it is reachable from a
|
|
14
|
+
* page's script, and a frame that did not answer is a frame whose clock did
|
|
15
|
+
* not move, which the run treats as the variant breaking rather than judging
|
|
16
|
+
* a game on clocks that disagree.
|
|
17
|
+
*
|
|
18
|
+
* Three pieces of Playwright's code are changed (patchClockSource): a timer's
|
|
19
|
+
* callback is called from a native timer task instead of from the clock's own
|
|
20
|
+
* loop, so a stack trace taken inside it holds the callback alone, as in
|
|
21
|
+
* production, and never the clock's frames (_callFirstTimer, _runTo); an
|
|
22
|
+
* exception it throws reaches the window as an uncaught error, as in
|
|
23
|
+
* production, where the run records it. An animation frame's callback is
|
|
24
|
+
* given the document's animation timeline reading (the embedder's
|
|
25
|
+
* `frameTime`, set by startClock: the native frame time plus the clock's
|
|
26
|
+
* lead, which is what the shimmed `document.timeline.currentTime` reads) in
|
|
27
|
+
* place of the clock's ticks, so inside the callback the two are equal as in
|
|
28
|
+
* production. And a read of the clock while it is paused from its real-time
|
|
29
|
+
* flow (inside a jump: runFor and fastForward pause it) asks the embedder to
|
|
30
|
+
* let the real time since the clock last moved flow into it (`flowInJump`,
|
|
31
|
+
* set by startClock), so inside a callback the jump fires performance.now()
|
|
32
|
+
* and Date.now() move across the callback's own work as they do in any
|
|
33
|
+
* browser, instead of standing still until the callback returns. The patch is
|
|
34
|
+
* applied to the exact text it expects and refused otherwise: a clock the run
|
|
35
|
+
* does not know is not a clock to travel on.
|
|
36
|
+
*
|
|
37
|
+
* A frame the game makes while the run is on (a nested page) installs its own
|
|
38
|
+
* clock at creation and reads the instant from its parent, same-origin, so it
|
|
39
|
+
* agrees with the frames that jumped before it existed; a frame with no
|
|
40
|
+
* same-origin parent (the top frames at launch) starts from the instant the
|
|
41
|
+
* run set, plus the real time since.
|
|
42
|
+
*/
|
|
43
|
+
import { createRequire } from 'node:module';
|
|
44
|
+
import { dirname, join } from 'node:path';
|
|
45
|
+
|
|
46
|
+
let cached = null;
|
|
47
|
+
|
|
48
|
+
// What the patch replaces, and with what (clockSource's `_callFirstTimer` and
|
|
49
|
+
// the embedder it dispatches through).
|
|
50
|
+
const DISPATCH_FROM = ` let error;
|
|
51
|
+
try {
|
|
52
|
+
timer.func.apply(null, args);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
error = e;
|
|
55
|
+
}
|
|
56
|
+
await new Promise((f) => this._embedder.setTimeout(f));
|
|
57
|
+
return { timerFound: true, error };`;
|
|
58
|
+
const DISPATCH_TO = ` await new Promise((f) => { this._embedder.callTimer(timer.func, args); this._embedder.setTimeout(f); });
|
|
59
|
+
return { timerFound: true, error: void 0 };`;
|
|
60
|
+
const EMBEDDER_FROM = ` setTimeout: (task, timeout) => {
|
|
61
|
+
const timerId = originals.bound.setTimeout(task, timeout);`;
|
|
62
|
+
const EMBEDDER_TO = ` callTimer: (func, args) => void originals.bound.setTimeout(func, 0, ...args),
|
|
63
|
+
setTimeout: (task, timeout) => {
|
|
64
|
+
const timerId = originals.bound.setTimeout(task, timeout);`;
|
|
65
|
+
const FRAME_FROM = ` if (timer.type === "AnimationFrame" /* AnimationFrame */)
|
|
66
|
+
args = [this._now.ticks];`;
|
|
67
|
+
const FRAME_TO = ` if (timer.type === "AnimationFrame" /* AnimationFrame */)
|
|
68
|
+
args = [typeof this._embedder.frameTime === "function" ? this._embedder.frameTime(this._now.ticks) : this._now.ticks];`;
|
|
69
|
+
// A read of the clock while it is paused from its real-time flow (inside a
|
|
70
|
+
// jump): the embedder may let the real time since flow into it.
|
|
71
|
+
const SYNC_FROM = ` _syncRealTime() {
|
|
72
|
+
if (!this._realTime)
|
|
73
|
+
return;`;
|
|
74
|
+
const SYNC_TO = ` _syncRealTime() {
|
|
75
|
+
if (!this._realTime) {
|
|
76
|
+
if (typeof this._embedder.flowInJump === "function")
|
|
77
|
+
this._embedder.flowInJump();
|
|
78
|
+
return;
|
|
79
|
+
}`;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Playwright's clock source with a timer's callback dispatched from a native
|
|
83
|
+
* timer task (see above): the callback goes to the native setTimeout as it is,
|
|
84
|
+
* with the timer's arguments, and the clock's loop waits for a task of its own
|
|
85
|
+
* queued right after it, so callbacks still run one per task and in order; an
|
|
86
|
+
* animation frame's callback given the embedder's frame time when it has one;
|
|
87
|
+
* and a read of the paused clock (inside a jump) handed to the embedder's
|
|
88
|
+
* `flowInJump` when it has one, so the clock can flow with the real time the
|
|
89
|
+
* jump's callbacks spend. All four pieces of text must be there exactly once,
|
|
90
|
+
* or the source is refused.
|
|
91
|
+
*/
|
|
92
|
+
export function patchClockSource(src) {
|
|
93
|
+
if (typeof src !== 'string') throw new Error('the clock source is not text');
|
|
94
|
+
for (const [from, what] of [[DISPATCH_FROM, 'timer dispatch'], [EMBEDDER_FROM, 'embedder'], [FRAME_FROM, 'animation frame argument'], [SYNC_FROM, 'real-time sync']]) {
|
|
95
|
+
const n = src.split(from).length - 1;
|
|
96
|
+
if (n !== 1) throw new Error(`playwright-core's clock source is not what the run expects: its ${what} appears ${n} time(s), not once`);
|
|
97
|
+
}
|
|
98
|
+
return src.replace(DISPATCH_FROM, DISPATCH_TO).replace(EMBEDDER_FROM, EMBEDDER_TO).replace(FRAME_FROM, FRAME_TO).replace(SYNC_FROM, SYNC_TO);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Playwright's clock implementation, as its own context.clock injects it: a
|
|
103
|
+
* script that leaves `inject` and `install` on `module.exports`. Read from the
|
|
104
|
+
* installed playwright-core, patched (patchClockSource), and refused when it
|
|
105
|
+
* is not what the run expects: a matrix on a clock the run does not know is
|
|
106
|
+
* not a matrix.
|
|
107
|
+
*/
|
|
108
|
+
export function clockSource() {
|
|
109
|
+
if (cached) return cached;
|
|
110
|
+
const require = createRequire(import.meta.url);
|
|
111
|
+
let file;
|
|
112
|
+
try { file = join(dirname(require.resolve('playwright-core')), 'lib', 'generated', 'clockSource.js'); } catch (e) { throw new Error(`playwright-core is not installed: ${e.message}`); }
|
|
113
|
+
let src;
|
|
114
|
+
try { src = require(file).source; } catch (e) { throw new Error(`playwright-core's clock source could not be read (${file}): ${e.message}`); }
|
|
115
|
+
if (typeof src !== 'string' || !src.includes('ClockController') || !src.includes('inject: () => inject') || !src.includes('module.exports')) throw new Error(`playwright-core's clock source is not what the run expects (${file})`);
|
|
116
|
+
cached = patchClockSource(src);
|
|
117
|
+
return cached;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Starts the fake clock in a scope (stringified into the page's init script,
|
|
122
|
+
* after the natives are captured and before anything else): Playwright's
|
|
123
|
+
* `inject` installs the fakes and lets them flow; `install(at)` sets the
|
|
124
|
+
* instant. `at` is the parent frame's now when the parent is the game's own
|
|
125
|
+
* (same-origin: a frame made mid-run agrees with the frames that jumped), else
|
|
126
|
+
* `o.at` plus the real time since `o.wall`, when the run wrote the script.
|
|
127
|
+
*
|
|
128
|
+
* The fake's monotonic clock is started where the native one stands (Playwright
|
|
129
|
+
* starts it at zero), so performance.now() and every native timestamp agree
|
|
130
|
+
* at boot and differ afterwards by the jumps alone. Those are kept: a history
|
|
131
|
+
* of what the fake clock was ahead of the native one after each move, read
|
|
132
|
+
* back by `leadAt` (the lead at a native instant: a timestamp taken before a
|
|
133
|
+
* jump keeps its time) and `toNative` (a fake instant as the native clock
|
|
134
|
+
* knows it). Inside a jump the clock flows: Playwright pauses its real-time
|
|
135
|
+
* flow for the jump, so the clock would stand still inside every callback the
|
|
136
|
+
* jump fires until the callback returned (a game measuring its own work
|
|
137
|
+
* against performance.now() in a timer would read zero, which no browser
|
|
138
|
+
* shows); here the real time since the clock last moved is let into it at
|
|
139
|
+
* every read inside the jump (the patched `_syncRealTime` asks
|
|
140
|
+
* `flowInJump`) and before every move (a timer's dispatch, the jump's end),
|
|
141
|
+
* so a callback that spends 3 ms sees 3 ms pass, the timer due next fires in
|
|
142
|
+
* order and late by what the callback took, as natively, and the jump still
|
|
143
|
+
* ends at its target with the callbacks' time inside it. And the jumps reach
|
|
144
|
+
* what the clock does not drive: every running animation (Web Animations,
|
|
145
|
+
* CSS) is moved on by what the clock moved, at its own rate, as the clock
|
|
146
|
+
* moves (before any timer due in the jump runs); the real time the jump
|
|
147
|
+
* itself took moves the animations on its own (the compositor's), and what
|
|
148
|
+
* of it the clock did not count is taken back at the end, so a transition
|
|
149
|
+
* finishes in game time, and what is on screen agrees with the clock. An
|
|
150
|
+
* animation frame's callback is handed the timeline's reading (the native
|
|
151
|
+
* frame time plus the lead), the same number `document.timeline.currentTime`
|
|
152
|
+
* gives inside it. The same in a worker (workerShim): no document, no
|
|
153
|
+
* animations, no parent; a timer global the scope never had (Playwright
|
|
154
|
+
* installs `requestIdleCallback` wherever it installs) is removed again, so
|
|
155
|
+
* the scope keeps its own shape. Returns what clockShim and clockTrap need of
|
|
156
|
+
* it: the fake to read, and the jump to drive (and the controller and the
|
|
157
|
+
* history, for the tests).
|
|
158
|
+
*/
|
|
159
|
+
export function startClock(scope, o, exportsOf, natives) {
|
|
160
|
+
// Playwright's bundle exports thunks (`inject: () => inject`): the function is one call in.
|
|
161
|
+
const inject = exportsOf.inject();
|
|
162
|
+
if (typeof inject !== 'function') throw new Error('the clock source exports no inject');
|
|
163
|
+
// The clock reads real time through the `performance` it finds at injection
|
|
164
|
+
// and calls `now` on it by name; clockShim will route that name to the fake,
|
|
165
|
+
// so the clock is given a stand-in holding the native `now` bound, and the
|
|
166
|
+
// native object is put back by the shim. Nothing the game sees of it.
|
|
167
|
+
const nativeNow = natives.perfNow.d.value.bind(natives.performance);
|
|
168
|
+
const standIn = { now: nativeNow, timeOrigin: natives.originValue };
|
|
169
|
+
Object.defineProperty(scope, 'performance', { value: standIn, writable: true, enumerable: true, configurable: true });
|
|
170
|
+
const pw = inject(scope);
|
|
171
|
+
if (!pw || !pw.controller || typeof pw.controller.install !== 'function') throw new Error('the clock source gave no controller');
|
|
172
|
+
// A timer the scope never had (a worker has no requestIdleCallback) is not given to it.
|
|
173
|
+
for (const k of ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'requestAnimationFrame', 'cancelAnimationFrame', 'requestIdleCallback', 'cancelIdleCallback']) {
|
|
174
|
+
if (typeof natives[k] !== 'function' && Object.getOwnPropertyDescriptor(scope, k)) { try { delete scope[k]; } catch { /* left as it is */ } }
|
|
175
|
+
}
|
|
176
|
+
// Playwright's fake performance, read here before clockShim puts the native object back: its now and
|
|
177
|
+
// timeOrigin are the fake clock's (the shimmed getters read them from here, never from themselves).
|
|
178
|
+
const fakePerf = scope.performance;
|
|
179
|
+
let at = null;
|
|
180
|
+
try { if (scope.parent && scope.parent !== scope && typeof scope.parent.Date === 'function') at = scope.parent.Date.now(); } catch { at = null; }
|
|
181
|
+
if (typeof at !== 'number' || !Number.isFinite(at)) at = o.at + (natives.Date.now() - o.wall);
|
|
182
|
+
const C = pw.controller;
|
|
183
|
+
C.install(at);
|
|
184
|
+
if (!C._now || typeof C._now.ticks !== 'number' || typeof C._now.time !== 'number' || typeof C._advanceNow !== 'function' || typeof C._innerPause !== 'function' || typeof C._innerResume !== 'function') throw new Error('the clock source is not the controller the run expects');
|
|
185
|
+
// The monotonic clock starts where the native one stands; the origin is the wall time at its zero.
|
|
186
|
+
C._now.ticks = Math.ceil(nativeNow());
|
|
187
|
+
C._now.origin = C._now.time - C._now.ticks;
|
|
188
|
+
// The jumps: (native instant, lead) after each move of the clock.
|
|
189
|
+
const history = [{ at: nativeNow(), lead: C._now.ticks - nativeNow() }];
|
|
190
|
+
const note = () => {
|
|
191
|
+
const t = nativeNow(), lead = C._now.ticks - t, last = history[history.length - 1];
|
|
192
|
+
if (t <= last.at) last.lead = lead;
|
|
193
|
+
else if (Math.abs(last.lead - lead) >= 1) history.push({ at: t, lead });
|
|
194
|
+
};
|
|
195
|
+
const leadAt = (t) => {
|
|
196
|
+
if (!(t > history[0].at)) return history[0].lead;
|
|
197
|
+
let lo = 0, hi = history.length - 1;
|
|
198
|
+
while (lo < hi) { const mid = (lo + hi + 1) >> 1; if (history[mid].at <= t) lo = mid; else hi = mid - 1; }
|
|
199
|
+
return history[lo].lead;
|
|
200
|
+
};
|
|
201
|
+
// (The fake's ticks are whole milliseconds: an instant within one of a
|
|
202
|
+
// move's own is the move's, never the move before.)
|
|
203
|
+
const toNative = (f) => {
|
|
204
|
+
for (let i = history.length - 1; i >= 0; i--) { const t = f - history[i].lead; if (t >= history[i].at - 1) return Math.max(t, history[i].at); }
|
|
205
|
+
return f - history[0].lead;
|
|
206
|
+
};
|
|
207
|
+
// Every running animation of the document moved by `ms` of its own time.
|
|
208
|
+
const advance = (ms) => {
|
|
209
|
+
if (!ms || !scope.document || !natives.getAnimations || !natives.animCurrent || !natives.animState) return;
|
|
210
|
+
let list;
|
|
211
|
+
try { list = natives.getAnimations.call(scope.document); } catch { return; }
|
|
212
|
+
for (const a of list) {
|
|
213
|
+
try {
|
|
214
|
+
if (natives.animState.d.get.call(a) !== 'running') continue;
|
|
215
|
+
const cur = natives.animCurrent.d.get.call(a);
|
|
216
|
+
if (typeof cur !== 'number') continue;
|
|
217
|
+
const rate = natives.animRate ? natives.animRate.d.get.call(a) : 1;
|
|
218
|
+
natives.animCurrent.d.set.call(a, cur + ms * (typeof rate === 'number' ? rate : 1));
|
|
219
|
+
} catch { /* an animation that cannot be moved stays where it is */ }
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
// What an animation frame's callback is handed: the document's native frame
|
|
223
|
+
// time plus the clock's lead now, which is what the shimmed
|
|
224
|
+
// document.timeline.currentTime reads inside the callback; the ticks where
|
|
225
|
+
// there is no timeline (a worker).
|
|
226
|
+
C._embedder.frameTime = (ticks) => {
|
|
227
|
+
try {
|
|
228
|
+
const tl = scope.document && scope.document.timeline;
|
|
229
|
+
const v = tl && natives.timeline && typeof natives.timeline.d.get === 'function' ? natives.timeline.d.get.call(tl) : null;
|
|
230
|
+
return typeof v === 'number' ? v + (C._now.ticks - nativeNow()) : ticks;
|
|
231
|
+
} catch { return ticks; }
|
|
232
|
+
};
|
|
233
|
+
const origAdvance = C._advanceNow, origPause = C._innerPause, origResume = C._innerResume;
|
|
234
|
+
// The real time of a jump flows into the clock (see above). `stamp` is the
|
|
235
|
+
// native instant the clock last counted up to while paused (whole
|
|
236
|
+
// milliseconds, as the embedder reads the clock; null while the clock flows
|
|
237
|
+
// in real time by itself), `counted` how much real time the flow put into
|
|
238
|
+
// the clock during this pause. The flow moves the clock alone, never the
|
|
239
|
+
// animations: the compositor moves those by the same real time itself.
|
|
240
|
+
const embNow = () => C._embedder.performanceNow();
|
|
241
|
+
let lostFrom = null, stamp = null, counted = 0;
|
|
242
|
+
const flow = () => {
|
|
243
|
+
if (stamp === null) return;
|
|
244
|
+
const t = embNow(), since = t - stamp;
|
|
245
|
+
if (since > 0) { stamp = t; counted += since; origAdvance.call(C, C._now.ticks + since); note(); }
|
|
246
|
+
};
|
|
247
|
+
C._embedder.flowInJump = flow;
|
|
248
|
+
// A move of the clock (not its real-time flow, which _realTime marks): the real time since the last flows first, then the animations by the rest, then the clock, then the history.
|
|
249
|
+
C._advanceNow = function (to) { if (!this._realTime) { flow(); if (to > this._now.ticks) advance(to - this._now.ticks); } const r = origAdvance.call(this, to); if (!this._realTime) note(); return r; };
|
|
250
|
+
// A jump pauses the real-time flow and resumes it after: the real time between is the clock's only as far as the flow counted it, so the rest is taken back from the animations.
|
|
251
|
+
C._innerPause = function (...a) { if (lostFrom === null) { lostFrom = nativeNow(); counted = 0; stamp = embNow(); } return origPause.apply(this, a); };
|
|
252
|
+
C._innerResume = function (...a) { stamp = null; if (lostFrom !== null) { advance(lostFrom + counted - nativeNow()); lostFrom = null; counted = 0; } const r = origResume.apply(this, a); note(); return r; };
|
|
253
|
+
return {
|
|
254
|
+
fake: { now: () => C.now(), perfNow: () => fakePerf.now(), origin: () => fakePerf.timeOrigin, leadAt, toNative },
|
|
255
|
+
jump: (op, ms) => C[op](ms),
|
|
256
|
+
controller: C,
|
|
257
|
+
history,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The site page's side of the channel (evaluated in the run's own page): posts
|
|
263
|
+
* the command into every window below it, itself included, and resolves with
|
|
264
|
+
* each window's clock and location once all have answered; the caller bounds
|
|
265
|
+
* the wait and reads `window.__clock` (the site page's own, out of the game's
|
|
266
|
+
* reach) for who did not answer.
|
|
267
|
+
*/
|
|
268
|
+
export function siteJump([token, op, ms]) {
|
|
269
|
+
return new Promise((resolve) => {
|
|
270
|
+
const id = Math.random().toString(36).slice(2, 12);
|
|
271
|
+
const posted = [];
|
|
272
|
+
const walk = (w) => {
|
|
273
|
+
try { w.postMessage(`${token}:clock:${op}:${id}:${ms}`, '*'); posted.push(w); } catch { /* a window that cannot be reached has no clock to move */ }
|
|
274
|
+
let n = 0;
|
|
275
|
+
try { n = w.frames.length; } catch { n = 0; }
|
|
276
|
+
for (let i = 0; i < n; i++) { try { walk(w.frames[i]); } catch { /* gone */ } }
|
|
277
|
+
};
|
|
278
|
+
const nows = [], from = [];
|
|
279
|
+
const prefix = `${token}:clockdone:${id}:`;
|
|
280
|
+
// The tally, for the run to read when a window did not answer in time.
|
|
281
|
+
const tally = { id, op, ms, posted: 0, from };
|
|
282
|
+
window.__clock = tally;
|
|
283
|
+
const done = (e) => {
|
|
284
|
+
if (typeof e.data !== 'string' || !e.data.startsWith(prefix)) return;
|
|
285
|
+
const [now, ...href] = e.data.slice(prefix.length).split(':');
|
|
286
|
+
nows.push(Number(now));
|
|
287
|
+
from.push(href.join(':'));
|
|
288
|
+
if (nows.length >= posted.length) { removeEventListener('message', done); resolve({ of: posted.length, nows, from }); }
|
|
289
|
+
};
|
|
290
|
+
addEventListener('message', done);
|
|
291
|
+
walk(window);
|
|
292
|
+
tally.posted = posted.length;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Package conformance holds what the conformance run and the production servers
|
|
2
|
+
// share: the engine's Content-Security-Policy headers, rendered from csp.json
|
|
3
|
+
// (the one source; run.mjs renders the same file), and the endpoint browsers
|
|
4
|
+
// send the policy's violation reports to.
|
|
5
|
+
package conformance
|
|
6
|
+
|
|
7
|
+
import (
|
|
8
|
+
_ "embed"
|
|
9
|
+
"encoding/json"
|
|
10
|
+
"strings"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
//go:embed csp.json
|
|
14
|
+
var cspJSON []byte
|
|
15
|
+
|
|
16
|
+
var templates struct {
|
|
17
|
+
Bundle string `json:"bundle"`
|
|
18
|
+
Shell string `json:"shell"`
|
|
19
|
+
Endpoints string `json:"endpoints"`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func init() {
|
|
23
|
+
// The file is part of the build: a template that does not parse is a defect
|
|
24
|
+
// of the build, found by the first test, never by a player.
|
|
25
|
+
if err := json.Unmarshal(cspJSON, &templates); err != nil {
|
|
26
|
+
panic("csp.json: " + err.Error())
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func render(template string, pairs ...string) string {
|
|
31
|
+
out := strings.NewReplacer(pairs...).Replace(template)
|
|
32
|
+
if strings.Contains(out, "{") {
|
|
33
|
+
panic("csp.json: a placeholder has no value in " + out)
|
|
34
|
+
}
|
|
35
|
+
return out
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// BundleCSP is the policy a studio bundle is served under: origin is the
|
|
39
|
+
// bundle's own ('self'), ancestors who may frame it, report where its
|
|
40
|
+
// browser sends violation reports (a path on the bundle's origin).
|
|
41
|
+
func BundleCSP(origin, ancestors, report string) string {
|
|
42
|
+
return render(templates.Bundle, "{origin}", origin, "{ancestors}", ancestors, "{report}", report)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ShellCSP is the shell's policy: frames are the origins it may frame (the
|
|
46
|
+
// bundles), ancestors who may frame it, report where its violation reports go.
|
|
47
|
+
func ShellCSP(frames, ancestors, report string) string {
|
|
48
|
+
return render(templates.Shell, "{frames}", frames, "{ancestors}", ancestors, "{report}", report)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ReportingEndpoints is the Reporting-Endpoints header a policy's `report-to
|
|
52
|
+
// csp` names: browsers with the Reporting API deliver there, the others to
|
|
53
|
+
// report-uri. Both point at the same path.
|
|
54
|
+
func ReportingEndpoints(report string) string {
|
|
55
|
+
return render(templates.Endpoints, "{report}", report)
|
|
56
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"about": "The two Content-Security-Policy headers of the House Engine, rendered by bundles.go, shell.go and run.mjs from this one file: {origin} is the bundle's own origin ('self' in production), {frames} the origins the shell may frame, {ancestors} who may frame the page, {report} the path browsers send violation reports to (report-to names the endpoint of the Reporting-Endpoints header, csp.go and csp.mjs render both). SPEC.md §4 explains each directive.",
|
|
3
|
+
"bundle": "sandbox allow-scripts allow-same-origin; default-src 'none'; script-src {origin} 'unsafe-inline' 'wasm-unsafe-eval'; worker-src {origin} blob:; style-src {origin} 'unsafe-inline'; img-src {origin} data: blob:; media-src {origin} data: blob:; font-src {origin} data:; connect-src {origin}; frame-src {origin}; frame-ancestors {ancestors}; base-uri 'none'; form-action 'none'; webrtc 'block'; report-uri {report}; report-to csp",
|
|
4
|
+
"shell": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; frame-src {frames}; img-src 'self' data:; frame-ancestors {ancestors}; base-uri 'none'; form-action 'none'; report-uri {report}; report-to csp",
|
|
5
|
+
"endpoints": "csp=\"{report}\""
|
|
6
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// The engine's Content-Security-Policy headers, rendered from csp.json: the
|
|
2
|
+
// one source the production servers (csp.go) render the same way, so the run
|
|
3
|
+
// plays a bundle under the policy players get.
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
const templates = JSON.parse(readFileSync(new URL('./csp.json', import.meta.url), 'utf8'));
|
|
7
|
+
|
|
8
|
+
function render(template, values) {
|
|
9
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
10
|
+
if (values[key] === undefined) throw new Error(`csp.json: no value for {${key}}`);
|
|
11
|
+
return values[key];
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** The bundle origin's policy: its own origin, who may frame it, where reports go. */
|
|
16
|
+
export const bundleCSP = ({ origin, ancestors, report }) => render(templates.bundle, { origin, ancestors, report });
|
|
17
|
+
/** The shell's policy: which origins it frames, who may frame it, where reports go. */
|
|
18
|
+
export const shellCSP = ({ frames, ancestors, report }) => render(templates.shell, { frames, ancestors, report });
|
|
19
|
+
/** The Reporting-Endpoints header that `report-to csp` names. */
|
|
20
|
+
export const reportingEndpoints = (report) => render(templates.endpoints, { report });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The gzip size of each file named in workerData, for scan.mjs: measured on a
|
|
2
|
+
// worker thread, so a bundle at the budget's edge (seconds of compression) never
|
|
3
|
+
// holds the event loop of the process that asked — the pipeline keeps answering
|
|
4
|
+
// its health checks and the studio while an upload is measured.
|
|
5
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { gzipSync } from 'node:zlib';
|
|
8
|
+
|
|
9
|
+
parentPort.postMessage(workerData.map((path) => gzipSync(readFileSync(path)).length));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "type": "module" }
|