commit b26c826b6bd413294174b1a7974b708fbad8148d parent 4f17b10f0c4832929ce6f4a14ed627f646d01247 Author: Brian Graham <brian@buildingbetterteams.de> Date: Tue, 7 Apr 2026 04:31:05 +0200 Add 99 new runs (472 total) Profile: main_effects Completed: 99 | Skipped: 6 | Failed: 18 Diffstat:
71 files changed, 19085 insertions(+), 747 deletions(-)
diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/index.html b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/index.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Tetris</title> + <link rel="stylesheet" href="styles.css"> +</head> +<body> + <div class="game-wrapper"> + <!-- Left Panel --> + <div class="side-panel"> + <div class="panel-box"> + <h3>Hold</h3> + <div id="hold-preview" class="preview-grid"></div> + </div> + <div class="panel-box controls-box"> + <h3>Controls</h3> + <div><kbd>←</kbd> <kbd>→</kbd> Move</div> + <div><kbd>↑</kbd> / <kbd>X</kbd> Rotate CW</div> + <div><kbd>Z</kbd> Rotate CCW</div> + <div><kbd>↓</kbd> Soft drop</div> + <div><kbd>Space</kbd> Hard drop</div> + <div><kbd>C</kbd> / <kbd>Shift</kbd> Hold</div> + <div><kbd>P</kbd> / <kbd>Esc</kbd> Pause</div> + </div> + </div> + + <!-- Board --> + <div class="board-container"> + <div class="board-frame"> + <div id="board" class="board"></div> + </div> + + <!-- Start Overlay --> + <div id="start-overlay" class="overlay"> + <h1>Tetris</h1> + <div class="subtitle">DOM-rendered · TypeScript</div> + <div class="prompt">Press Enter or Space to start</div> + </div> + + <!-- Pause Overlay --> + <div id="pause-overlay" class="overlay hidden"> + <h1>Paused</h1> + <div class="prompt">Press P or Esc to resume</div> + </div> + + <!-- Game Over Overlay --> + <div id="gameover-overlay" class="overlay hidden"> + <h2>Game Over</h2> + <div class="subtitle">Score: <span id="final-score">0</span></div> + <div class="prompt">Press Enter to play again</div> + </div> + </div> + + <!-- Right Panel --> + <div class="side-panel"> + <div class="panel-box"> + <h3>Next</h3> + <div id="next-preview" class="preview-grid"></div> + </div> + <div class="panel-box"> + <h3>Score</h3> + <div id="score-value" class="value">0</div> + </div> + <div class="panel-box"> + <h3>Level</h3> + <div id="level-value" class="value">1</div> + </div> + <div class="panel-box"> + <h3>Lines</h3> + <div id="lines-value" class="value">0</div> + </div> + </div> + </div> + + <script src="tetris.js"></script> +</body> +</html> diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package-lock.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package-lock.json @@ -0,0 +1,2519 @@ +{ + "name": "loop-bench-4v9zg5jy", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "loop-bench-4v9zg5jy", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "html-validate": "^10.11.3", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.4.tgz", + "integrity": "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.4", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.4.tgz", + "integrity": "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", + "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", + "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", + "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@html-validate/stylish": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@html-validate/stylish/-/stylish-5.1.0.tgz", + "integrity": "sha512-Tyx/ZbHBpVZjvSleNplNMUhqT4UY1HwAMC97GSmasJXggWuvjNFLBS2scqnEb+ZG1szLq4zgjOioj7cVWV9WuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.0.0" + }, + "engines": { + "node": "^20.11 || >= 22.16" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jscpd/badge-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/badge-reporter/-/badge-reporter-4.0.4.tgz", + "integrity": "sha512-I9b4MmLXPM2vo0SxSUWnNGKcA4PjQlD3GzXvFK60z43cN/EIdLbOq3FVwCL+dg2obUqGXKIzAm7EsDFTg0D+mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "badgen": "^3.2.3", + "colors": "^1.4.0", + "fs-extra": "^11.2.0" + } + }, + "node_modules/@jscpd/core": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/core/-/core-4.0.4.tgz", + "integrity": "sha512-QGMT3iXEX1fI6lgjPH+x8eyJwhwr2KkpSF5uBpjC0Z5Xloj0yFTFLtwJT+RhxP/Ob4WYrtx2jvpKB269oIwgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/@jscpd/finder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/finder/-/finder-4.0.4.tgz", + "integrity": "sha512-qVUWY7Nzuvfd5OIk+n7/5CM98LmFroLqblRXAI2gDABwZrc7qS+WH2SNr0qoUq0f4OqwM+piiwKvwL/VDNn/Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "blamer": "^1.0.6", + "bytes": "^3.1.2", + "cli-table3": "^0.6.5", + "colors": "^1.4.0", + "fast-glob": "^3.3.2", + "fs-extra": "^11.2.0", + "markdown-table": "^2.0.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/html-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/html-reporter/-/html-reporter-4.0.4.tgz", + "integrity": "sha512-YiepyeYkeH74Kx59PJRdUdonznct0wHPFkf6FLQN+mCBoy6leAWCcOfHtcexnp+UsBFDlItG5nRdKrDSxSH+Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "1.4.0", + "fs-extra": "^11.2.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/tokenizer": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/tokenizer/-/tokenizer-4.0.4.tgz", + "integrity": "sha512-xxYYY/qaLah/FlwogEbGIxx9CjDO+G9E6qawcy26WwrflzJb6wsnhjwdneN6Wb0RNCDsqvzY+bzG453jsin4UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "reprism": "^0.0.11", + "spark-md5": "^3.0.2" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/badgen": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/badgen/-/badgen-3.2.3.tgz", + "integrity": "sha512-svDuwkc63E/z0ky3drpUppB83s/nlgDciH9m+STwwQoWyq7yCgew1qEfJ+9axkKdNq7MskByptWUN9j1PGMwFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/blamer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/blamer/-/blamer-1.0.7.tgz", + "integrity": "sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^4.0.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gitignore-to-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitignore-to-glob/-/gitignore-to-glob-0.3.0.tgz", + "integrity": "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.4 <5 || >=6.9" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-validate": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-10.11.3.tgz", + "integrity": "sha512-wKUq9iR6bukMgiHhs/ORThZzEbQoFiiPNN7aZfQ8dlmhttPb2sM2Ji2p+Fy5Xj1aH7QHJ1biT2SUDw7A01P2oA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/html-validate" + } + ], + "license": "MIT", + "dependencies": { + "@html-validate/stylish": "^5.0.0", + "@sidvind/better-ajv-errors": "4.0.1", + "ajv": "^8.0.0", + "glob": "^13.0.0", + "kleur": "^4.1.0", + "minimist": "^1.2.0", + "prompts": "^2.0.0", + "semver": "^7.0.0" + }, + "bin": { + "html-validate": "bin/html-validate.mjs" + }, + "engines": { + "node": "^20.19.0 || >= 22.16.0" + }, + "peerDependencies": { + "jest": "^28.1.3 || ^29.0.3 || ^30.0.0", + "jest-diff": "^28.1.3 || ^29.0.3 || ^30.0.0", + "jest-snapshot": "^28.1.3 || ^29.0.3 || ^30.0.0", + "vitest": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.1" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + }, + "jest-diff": { + "optional": true + }, + "jest-snapshot": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/html-validate/node_modules/@sidvind/better-ajv-errors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sidvind/better-ajv-errors/-/better-ajv-errors-4.0.1.tgz", + "integrity": "sha512-6arF1ssKxItxgitPYXafUoLmsVBA6K7m9+ZGj6hLDoBl7nWpJ33EInwQUdHTle2METeWGxgQiqSex20KZRykew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "kleur": "^4.1.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "ajv": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/html-validate/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/html-validate/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jscpd": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-4.0.8.tgz", + "integrity": "sha512-d2VNT/2Hv4dxT2/59He8Lyda4DYOxPRyRG9zBaOpTZAqJCVf2xLrBlZkT8Va6Lo9u3X2qz8Bpq4HrDi4JsrQhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/badge-reporter": "4.0.4", + "@jscpd/core": "4.0.4", + "@jscpd/finder": "4.0.4", + "@jscpd/html-reporter": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "colors": "^1.4.0", + "commander": "^5.0.0", + "fs-extra": "^11.2.0", + "gitignore-to-glob": "^0.3.0", + "jscpd-sarif-reporter": "4.0.6" + }, + "bin": { + "jscpd": "bin/jscpd" + } + }, + "node_modules/jscpd-sarif-reporter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/jscpd-sarif-reporter/-/jscpd-sarif-reporter-4.0.6.tgz", + "integrity": "sha512-b9Sm3IPZ3+m8Lwa4gZa+4/LhDhlc/ZLEsLXKSOy1DANQ6kx0ueqZT+fUHWEdQ6m0o3+RIVIa7DmvLSojQD05ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "fs-extra": "^11.2.0", + "node-sarif-builder": "^3.4.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz", + "integrity": "sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pug": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", + "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.4", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", + "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/reprism": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/reprism/-/reprism-0.0.11.tgz", + "integrity": "sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package.json @@ -0,0 +1,21 @@ +{ + "name": "loop-bench-4v9zg5jy", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "html-validate": "^10.11.3", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/index.html b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/index.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Tetris</title> + <link rel="stylesheet" href="styles.css"> +</head> +<body> + <div class="game-wrapper"> + <!-- Left Panel --> + <div class="side-panel"> + <div class="panel-box"> + <h3>Hold</h3> + <div id="hold-preview" class="preview-grid"></div> + </div> + <div class="panel-box controls-box"> + <h3>Controls</h3> + <div><kbd>←</kbd> <kbd>→</kbd> Move</div> + <div><kbd>↑</kbd> / <kbd>X</kbd> Rotate CW</div> + <div><kbd>Z</kbd> Rotate CCW</div> + <div><kbd>↓</kbd> Soft drop</div> + <div><kbd>Space</kbd> Hard drop</div> + <div><kbd>C</kbd> / <kbd>Shift</kbd> Hold</div> + <div><kbd>P</kbd> / <kbd>Esc</kbd> Pause</div> + </div> + </div> + + <!-- Board --> + <div class="board-container"> + <div class="board-frame"> + <div id="board" class="board"></div> + </div> + + <!-- Start Overlay --> + <div id="start-overlay" class="overlay"> + <h1>Tetris</h1> + <div class="subtitle">DOM-rendered · TypeScript</div> + <div class="prompt">Press Enter or Space to start</div> + </div> + + <!-- Pause Overlay --> + <div id="pause-overlay" class="overlay hidden"> + <h1>Paused</h1> + <div class="prompt">Press P or Esc to resume</div> + </div> + + <!-- Game Over Overlay --> + <div id="gameover-overlay" class="overlay hidden"> + <h2>Game Over</h2> + <div class="subtitle">Score: <span id="final-score">0</span></div> + <div class="prompt">Press Enter to play again</div> + </div> + </div> + + <!-- Right Panel --> + <div class="side-panel"> + <div class="panel-box"> + <h3>Next</h3> + <div id="next-preview" class="preview-grid"></div> + </div> + <div class="panel-box"> + <h3>Score</h3> + <div id="score-value" class="value">0</div> + </div> + <div class="panel-box"> + <h3>Level</h3> + <div id="level-value" class="value">1</div> + </div> + <div class="panel-box"> + <h3>Lines</h3> + <div id="lines-value" class="value">0</div> + </div> + </div> + </div> + + <script src="tetris.js"></script> +</body> +</html> diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/styles.css b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/styles.css @@ -0,0 +1,224 @@ +/* ===== Reset & Base ===== */ +*, +*::before, +*::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + background: #0a0a1a; + color: #e0e0e0; + font-family: "Segoe UI", system-ui, -apple-system, sans-serif; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + overflow: hidden; +} + +/* ===== Layout ===== */ +.game-wrapper { + display: flex; + gap: 24px; + align-items: flex-start; +} + +.side-panel { + display: flex; + flex-direction: column; + gap: 16px; + width: 140px; +} + +/* ===== Board ===== */ +.board-frame { + background: linear-gradient(135deg, #1a1a3e, #12122a); + border: 3px solid #3a3a6e; + border-radius: 6px; + padding: 4px; + box-shadow: 0 0 30px rgba(80, 60, 200, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.5); +} + +.board { + display: grid; + grid-template-columns: repeat(10, 28px); + grid-template-rows: repeat(20, 28px); + gap: 1px; + background: #0d0d20; +} + +.cell { + width: 28px; + height: 28px; + background: #111128; + border-radius: 2px; + transition: background-color 0.05s; +} + +.cell.filled { + border-radius: 3px; + box-shadow: inset 0 0 4px rgba(255, 255, 255, 0.15); +} + +/* Piece colours */ +.cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3, inset 0 0 6px rgba(255,255,255,0.2); } +.cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300, inset 0 0 6px rgba(255,255,255,0.2); } +.cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae, inset 0 0 6px rgba(255,255,255,0.2); } +.cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300, inset 0 0 6px rgba(255,255,255,0.2); } +.cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030, inset 0 0 6px rgba(255,255,255,0.2); } +.cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3, inset 0 0 6px rgba(255,255,255,0.2); } +.cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600, inset 0 0 6px rgba(255,255,255,0.2); } + +/* Ghost piece */ +.cell.ghost { + background: rgba(255, 255, 255, 0.08); + border: 1px dashed rgba(255, 255, 255, 0.25); + box-shadow: none; +} + +/* Line clear flash */ +.cell.clearing { + animation: line-clear 0.4s ease-out; +} + +@keyframes line-clear { + 0% { background: #fff; transform: scale(1.1); } + 50% { background: #fff; transform: scale(1.05); } + 100% { background: #111128; transform: scale(1); } +} + +/* ===== Panel Boxes ===== */ +.panel-box { + background: linear-gradient(135deg, #1a1a3e, #12122a); + border: 2px solid #3a3a6e; + border-radius: 6px; + padding: 12px; + text-align: center; +} + +.panel-box h3 { + font-size: 11px; + text-transform: uppercase; + letter-spacing: 2px; + color: #8888bb; + margin-bottom: 8px; +} + +.panel-box .value { + font-size: 22px; + font-weight: 700; + color: #ffffff; + font-variant-numeric: tabular-nums; +} + +/* ===== Next / Hold Previews ===== */ +.preview-grid { + display: grid; + grid-template-columns: repeat(4, 22px); + grid-template-rows: repeat(4, 22px); + gap: 1px; + justify-content: center; + margin: 0 auto; +} + +.preview-cell { + width: 22px; + height: 22px; + background: transparent; + border-radius: 2px; +} + +.preview-cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3; } +.preview-cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300; } +.preview-cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae; } +.preview-cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300; } +.preview-cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030; } +.preview-cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3; } +.preview-cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600; } + +/* ===== Controls Info ===== */ +.controls-box { + font-size: 11px; + line-height: 1.8; + color: #9999bb; +} + +.controls-box kbd { + display: inline-block; + background: #22224a; + border: 1px solid #44447a; + border-radius: 3px; + padding: 1px 6px; + font-family: inherit; + font-size: 10px; + color: #ccccee; + min-width: 22px; + text-align: center; +} + +/* ===== Overlays ===== */ +.overlay { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background: rgba(5, 5, 20, 0.88); + border-radius: 6px; + z-index: 10; + transition: opacity 0.3s; +} + +.overlay.hidden { + opacity: 0; + pointer-events: none; +} + +.overlay h1 { + font-size: 28px; + margin-bottom: 8px; + letter-spacing: 4px; + text-transform: uppercase; +} + +.overlay h2 { + font-size: 20px; + margin-bottom: 16px; + color: #ff4466; +} + +.overlay .subtitle { + font-size: 13px; + color: #8888bb; + margin-bottom: 20px; +} + +.overlay .prompt { + font-size: 14px; + color: #aaaadd; + animation: blink 1.2s ease-in-out infinite; +} + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +.board-container { + position: relative; +} + +/* ===== Responsive ===== */ +@media (max-height: 640px) { + .board { + grid-template-columns: repeat(10, 22px); + grid-template-rows: repeat(20, 22px); + } + .cell { + width: 22px; + height: 22px; + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/tetris.js b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/tetris.js @@ -0,0 +1,687 @@ +"use strict"; +const COLS = 10; +const ROWS = 20; +const HIDDEN_ROWS = 2; +const SHAPES = { + I: [ + [ + [0, 0, 0, 0], + [1, 1, 1, 1], + [0, 0, 0, 0], + [0, 0, 0, 0], + ], + [ + [0, 0, 1, 0], + [0, 0, 1, 0], + [0, 0, 1, 0], + [0, 0, 1, 0], + ], + [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 1], + [0, 0, 0, 0], + ], + [ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + ], + ], + O: [ + [ + [1, 1], + [1, 1], + ], + [ + [1, 1], + [1, 1], + ], + [ + [1, 1], + [1, 1], + ], + [ + [1, 1], + [1, 1], + ], + ], + T: [ + [ + [0, 1, 0], + [1, 1, 1], + [0, 0, 0], + ], + [ + [0, 1, 0], + [0, 1, 1], + [0, 1, 0], + ], + [ + [0, 0, 0], + [1, 1, 1], + [0, 1, 0], + ], + [ + [0, 1, 0], + [1, 1, 0], + [0, 1, 0], + ], + ], + S: [ + [ + [0, 1, 1], + [1, 1, 0], + [0, 0, 0], + ], + [ + [0, 1, 0], + [0, 1, 1], + [0, 0, 1], + ], + [ + [0, 0, 0], + [0, 1, 1], + [1, 1, 0], + ], + [ + [1, 0, 0], + [1, 1, 0], + [0, 1, 0], + ], + ], + Z: [ + [ + [1, 1, 0], + [0, 1, 1], + [0, 0, 0], + ], + [ + [0, 0, 1], + [0, 1, 1], + [0, 1, 0], + ], + [ + [0, 0, 0], + [1, 1, 0], + [0, 1, 1], + ], + [ + [0, 1, 0], + [1, 1, 0], + [1, 0, 0], + ], + ], + J: [ + [ + [1, 0, 0], + [1, 1, 1], + [0, 0, 0], + ], + [ + [0, 1, 1], + [0, 1, 0], + [0, 1, 0], + ], + [ + [0, 0, 0], + [1, 1, 1], + [0, 0, 1], + ], + [ + [0, 1, 0], + [0, 1, 0], + [1, 1, 0], + ], + ], + L: [ + [ + [0, 0, 1], + [1, 1, 1], + [0, 0, 0], + ], + [ + [0, 1, 0], + [0, 1, 0], + [0, 1, 1], + ], + [ + [0, 0, 0], + [1, 1, 1], + [1, 0, 0], + ], + [ + [1, 1, 0], + [0, 1, 0], + [0, 1, 0], + ], + ], +}; +const WALL_KICKS_JLSTZ = { + "0>1": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]], + "1>0": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]], + "1>2": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]], + "2>1": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]], + "2>3": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]], + "3>2": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]], + "3>0": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]], + "0>3": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]], +}; +const WALL_KICKS_I = { + "0>1": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]], + "1>0": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]], + "1>2": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]], + "2>1": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]], + "2>3": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]], + "3>2": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]], + "3>0": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]], + "0>3": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]], +}; +const LINE_SCORES = { + 1: 100, + 2: 300, + 3: 500, + 4: 800, +}; +const PIECE_TYPES = ["I", "O", "T", "S", "Z", "J", "L"]; +function createCell(className) { + const div = document.createElement("div"); + div.className = className; + return div; +} +function buildBoardDOM() { + const boardEl = document.getElementById("board"); + boardEl.innerHTML = ""; + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + boardEl.appendChild(createCell("cell")); + } + } + return boardEl; +} +function buildPreviewDOM(id) { + const el = document.getElementById(id); + el.innerHTML = ""; + for (let r = 0; r < 4; r++) { + for (let c = 0; c < 4; c++) { + el.appendChild(createCell("preview-cell")); + } + } + return el; +} +let board; +let cells; +let nextPreviewCells; +let holdPreviewCells; +let currentPiece; +let nextType; +let holdType; +let canHold; +let score; +let level; +let linesCleared; +let combo; +let gameOver; +let paused; +let started; +let clearing; +let dropTimer; +let lastTime; +let animFrameId; +let bag = []; +function initBoard() { + board = []; + for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) { + board.push(new Array(COLS).fill(null)); + } +} +function initGame() { + initBoard(); + score = 0; + level = 0; + linesCleared = 0; + combo = -1; + gameOver = false; + paused = false; + clearing = false; + holdType = null; + canHold = true; + bag = []; + nextType = pullFromBag(); + currentPiece = null; + dropTimer = 0; + lastTime = 0; + spawnPiece(); +} +function fillBag() { + const b = [...PIECE_TYPES]; + for (let i = b.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [b[i], b[j]] = [b[j], b[i]]; + } + bag = b; +} +function pullFromBag() { + if (bag.length === 0) + fillBag(); + return bag.pop(); +} +function spawnPiece() { + const type = nextType; + nextType = pullFromBag(); + canHold = true; + const shape = SHAPES[type][0]; + const x = Math.floor((COLS - shape[0].length) / 2); + const y = HIDDEN_ROWS - shape.length; + currentPiece = { type, shape, pos: { x, y }, rotation: 0 }; + if (!isValid(shape, currentPiece.pos)) { + gameOver = true; + currentPiece = null; + showOverlay("gameover"); + } + renderPreview(nextPreviewCells, nextType); + renderHoldPreview(); +} +function isValid(shape, pos) { + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const bx = pos.x + c; + const by = pos.y + r; + if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) + return false; + if (by >= 0 && board[by][bx] !== null) + return false; + } + } + return true; +} +function movePiece(dx, dy) { + if (!currentPiece) + return false; + const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy }; + if (isValid(currentPiece.shape, newPos)) { + currentPiece.pos = newPos; + return true; + } + return false; +} +function moveDown() { + if (!currentPiece) + return false; + if (movePiece(0, 1)) + return true; + lockPiece(); + return false; +} +function rotatePiece(dir) { + if (!currentPiece) + return; + const oldRot = currentPiece.rotation; + const newRot = (oldRot + dir + 4) % 4; + const newShape = SHAPES[currentPiece.type][newRot]; + const kickKey = `${oldRot}>${newRot}`; + const kicks = currentPiece.type === "I" + ? (WALL_KICKS_I[kickKey] || [[0, 0]]) + : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]); + for (const [kx, ky] of kicks) { + const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky }; + if (isValid(newShape, testPos)) { + currentPiece.shape = newShape; + currentPiece.pos = testPos; + currentPiece.rotation = newRot; + return; + } + } +} +function hardDrop() { + if (!currentPiece) + return; + let dropped = 0; + while (movePiece(0, 1)) { + dropped++; + } + score += dropped * 2; + lockPiece(); +} +function getGhostY() { + if (!currentPiece) + return 0; + let ghostY = currentPiece.pos.y; + const testPos = { x: currentPiece.pos.x, y: ghostY }; + while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) { + testPos.y++; + } + return testPos.y; +} +function lockPiece() { + if (!currentPiece) + return; + const { shape, pos, type } = currentPiece; + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const by = pos.y + r; + const bx = pos.x + c; + if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) { + board[by][bx] = type; + } + } + } + currentPiece = null; + const clearedRows = []; + for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) { + if (board[r].every((cell) => cell !== null)) { + clearedRows.push(r); + } + } + if (clearedRows.length > 0) { + combo++; + clearing = true; + animateClear(clearedRows, () => { + removeLines(clearedRows); + const n = clearedRows.length; + const baseScore = LINE_SCORES[n] || 0; + score += baseScore * (level + 1); + if (combo > 0) { + score += 50 * combo * (level + 1); + } + linesCleared += n; + level = Math.floor(linesCleared / 10); + updateStats(); + clearing = false; + spawnPiece(); + }); + } + else { + combo = -1; + updateStats(); + spawnPiece(); + } +} +function animateClear(rows, callback) { + for (const r of rows) { + for (let c = 0; c < COLS; c++) { + const boardRow = r - HIDDEN_ROWS; + if (boardRow >= 0 && boardRow < ROWS) { + const idx = boardRow * COLS + c; + cells[idx].classList.add("clearing"); + } + } + } + setTimeout(callback, 400); +} +function removeLines(rows) { + rows.sort((a, b) => b - a); + for (const r of rows) { + board.splice(r, 1); + board.unshift(new Array(COLS).fill(null)); + } +} +function holdPiece() { + if (!currentPiece || !canHold) + return; + canHold = false; + const type = currentPiece.type; + if (holdType !== null) { + const prevHold = holdType; + holdType = type; + currentPiece = null; + const shape = SHAPES[prevHold][0]; + const x = Math.floor((COLS - shape[0].length) / 2); + const y = HIDDEN_ROWS - shape.length; + currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 }; + } + else { + holdType = type; + currentPiece = null; + spawnPiece(); + } + renderHoldPreview(); +} +function render() { + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + const idx = r * COLS + c; + const cell = cells[idx]; + const boardR = r + HIDDEN_ROWS; + cell.className = "cell"; + const value = board[boardR][c]; + if (value) { + cell.classList.add("filled", value); + } + } + } + if (currentPiece) { + const ghostY = getGhostY(); + if (ghostY !== currentPiece.pos.y) { + renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, "ghost", currentPiece.type); + } + renderShape(currentPiece.shape, currentPiece.pos, "filled", currentPiece.type); + } +} +function renderShape(shape, pos, cls, type) { + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const by = pos.y + r; + const bx = pos.x + c; + const boardRow = by - HIDDEN_ROWS; + if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) { + const idx = boardRow * COLS + bx; + cells[idx].className = `cell ${cls} ${cls === "ghost" ? "" : type}`.trim(); + if (cls === "ghost") { + cells[idx].className = "cell ghost"; + } + } + } + } +} +function renderPreview(previewCells, type) { + const shape = SHAPES[type][0]; + const offR = Math.floor((4 - shape.length) / 2); + const offC = Math.floor((4 - shape[0].length) / 2); + for (let r = 0; r < 4; r++) { + for (let c = 0; c < 4; c++) { + const idx = r * 4 + c; + previewCells[idx].className = "preview-cell"; + const sr = r - offR; + const sc = c - offC; + if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) { + previewCells[idx].classList.add(type); + } + } + } +} +function renderHoldPreview() { + if (holdType) { + renderPreview(holdPreviewCells, holdType); + } + else { + for (const cell of holdPreviewCells) { + cell.className = "preview-cell"; + } + } +} +function updateStats() { + document.getElementById("score-value").textContent = score.toLocaleString(); + document.getElementById("level-value").textContent = String(level + 1); + document.getElementById("lines-value").textContent = String(linesCleared); +} +function getDropInterval() { + const speeds = [ + 800, 720, 630, 550, 470, 380, 300, 220, 150, 100, + 80, 70, 60, 50, 40, 30, 20, 15, 10, 8, + ]; + if (level < speeds.length) + return speeds[level]; + return 5; +} +function gameLoop(timestamp) { + if (gameOver || paused || !started) + return; + if (clearing) { + animFrameId = requestAnimationFrame(gameLoop); + return; + } + if (lastTime === 0) + lastTime = timestamp; + const delta = timestamp - lastTime; + lastTime = timestamp; + updateDAS(delta); + dropTimer += delta; + const interval = getDropInterval(); + if (dropTimer >= interval) { + dropTimer -= interval; + moveDown(); + } + render(); + animFrameId = requestAnimationFrame(gameLoop); +} +let dasKey = null; +let dasTimer = 0; +let dasActive = false; +const DAS_DELAY = 170; +const DAS_RATE = 50; +function handleKeyDown(e) { + if (!started || gameOver) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + startGame(); + } + return; + } + if (e.key === "p" || e.key === "P" || e.key === "Escape") { + togglePause(); + return; + } + if (paused) + return; + switch (e.key) { + case "ArrowLeft": + e.preventDefault(); + if (!dasActive) + movePiece(-1, 0); + dasKey = "left"; + dasTimer = 0; + dasActive = false; + break; + case "ArrowRight": + e.preventDefault(); + if (!dasActive) + movePiece(1, 0); + dasKey = "right"; + dasTimer = 0; + dasActive = false; + break; + case "ArrowDown": + e.preventDefault(); + if (moveDown()) { + score += 1; + updateStats(); + } + break; + case "ArrowUp": + case "x": + case "X": + e.preventDefault(); + rotatePiece(1); + break; + case "z": + case "Z": + e.preventDefault(); + rotatePiece(-1); + break; + case " ": + e.preventDefault(); + hardDrop(); + break; + case "c": + case "C": + case "Shift": + e.preventDefault(); + holdPiece(); + break; + } + render(); +} +function handleKeyUp(e) { + if (e.key === "ArrowLeft" && dasKey === "left") + dasKey = null; + if (e.key === "ArrowRight" && dasKey === "right") + dasKey = null; +} +function updateDAS(delta) { + if (dasKey === null) + return; + dasTimer += delta; + if (!dasActive && dasTimer >= DAS_DELAY) { + dasActive = true; + dasTimer = 0; + } + if (dasActive && dasTimer >= DAS_RATE) { + dasTimer -= DAS_RATE; + if (dasKey === "left") + movePiece(-1, 0); + else if (dasKey === "right") + movePiece(1, 0); + render(); + } +} +function togglePause() { + paused = !paused; + const overlay = document.getElementById("pause-overlay"); + if (paused) { + overlay.classList.remove("hidden"); + } + else { + overlay.classList.add("hidden"); + lastTime = 0; + animFrameId = requestAnimationFrame(gameLoop); + } +} +function showOverlay(which) { + const startOverlay = document.getElementById("start-overlay"); + const gameoverOverlay = document.getElementById("gameover-overlay"); + startOverlay.classList.add("hidden"); + gameoverOverlay.classList.add("hidden"); + if (which === "start") { + startOverlay.classList.remove("hidden"); + } + else { + document.getElementById("final-score").textContent = score.toLocaleString(); + gameoverOverlay.classList.remove("hidden"); + } +} +function hideAllOverlays() { + document.getElementById("start-overlay").classList.add("hidden"); + document.getElementById("gameover-overlay").classList.add("hidden"); + document.getElementById("pause-overlay").classList.add("hidden"); +} +function startGame() { + hideAllOverlays(); + initGame(); + started = true; + lastTime = 0; + dropTimer = 0; + updateStats(); + render(); + animFrameId = requestAnimationFrame(gameLoop); +} +function boot() { + const boardEl = buildBoardDOM(); + cells = Array.from(boardEl.querySelectorAll(".cell")); + buildPreviewDOM("next-preview"); + buildPreviewDOM("hold-preview"); + nextPreviewCells = Array.from(document.getElementById("next-preview").querySelectorAll(".preview-cell")); + holdPreviewCells = Array.from(document.getElementById("hold-preview").querySelectorAll(".preview-cell")); + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("keyup", handleKeyUp); + initBoard(); + showOverlay("start"); +} +document.addEventListener("DOMContentLoaded", boot); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/tetris.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/src/tetris.ts @@ -0,0 +1,816 @@ +// ============================================================ +// Tetris – DOM-rendered, TypeScript +// ============================================================ + +// ----- Types & Constants ----- + +type PieceType = "I" | "O" | "T" | "S" | "Z" | "J" | "L"; + +interface Position { + x: number; + y: number; +} + +interface ActivePiece { + type: PieceType; + shape: number[][]; + pos: Position; + rotation: number; +} + +const COLS = 10; +const ROWS = 20; +const HIDDEN_ROWS = 2; // buffer rows above visible area + +// Shapes stored as 4×4 (or smaller) matrices per rotation state +// Using the SRS-like representations +const SHAPES: Record<PieceType, number[][][]> = { + I: [ + [ + [0, 0, 0, 0], + [1, 1, 1, 1], + [0, 0, 0, 0], + [0, 0, 0, 0], + ], + [ + [0, 0, 1, 0], + [0, 0, 1, 0], + [0, 0, 1, 0], + [0, 0, 1, 0], + ], + [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 1], + [0, 0, 0, 0], + ], + [ + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + ], + ], + O: [ + [ + [1, 1], + [1, 1], + ], + [ + [1, 1], + [1, 1], + ], + [ + [1, 1], + [1, 1], + ], + [ + [1, 1], + [1, 1], + ], + ], + T: [ + [ + [0, 1, 0], + [1, 1, 1], + [0, 0, 0], + ], + [ + [0, 1, 0], + [0, 1, 1], + [0, 1, 0], + ], + [ + [0, 0, 0], + [1, 1, 1], + [0, 1, 0], + ], + [ + [0, 1, 0], + [1, 1, 0], + [0, 1, 0], + ], + ], + S: [ + [ + [0, 1, 1], + [1, 1, 0], + [0, 0, 0], + ], + [ + [0, 1, 0], + [0, 1, 1], + [0, 0, 1], + ], + [ + [0, 0, 0], + [0, 1, 1], + [1, 1, 0], + ], + [ + [1, 0, 0], + [1, 1, 0], + [0, 1, 0], + ], + ], + Z: [ + [ + [1, 1, 0], + [0, 1, 1], + [0, 0, 0], + ], + [ + [0, 0, 1], + [0, 1, 1], + [0, 1, 0], + ], + [ + [0, 0, 0], + [1, 1, 0], + [0, 1, 1], + ], + [ + [0, 1, 0], + [1, 1, 0], + [1, 0, 0], + ], + ], + J: [ + [ + [1, 0, 0], + [1, 1, 1], + [0, 0, 0], + ], + [ + [0, 1, 1], + [0, 1, 0], + [0, 1, 0], + ], + [ + [0, 0, 0], + [1, 1, 1], + [0, 0, 1], + ], + [ + [0, 1, 0], + [0, 1, 0], + [1, 1, 0], + ], + ], + L: [ + [ + [0, 0, 1], + [1, 1, 1], + [0, 0, 0], + ], + [ + [0, 1, 0], + [0, 1, 0], + [0, 1, 1], + ], + [ + [0, 0, 0], + [1, 1, 1], + [1, 0, 0], + ], + [ + [1, 1, 0], + [0, 1, 0], + [0, 1, 0], + ], + ], +}; + +// Wall kick data (SRS standard) +// For J, L, S, T, Z pieces: +const WALL_KICKS_JLSTZ: Record<string, [number, number][]> = { + "0>1": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]], + "1>0": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]], + "1>2": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]], + "2>1": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]], + "2>3": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]], + "3>2": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]], + "3>0": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]], + "0>3": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]], +}; + +// For I piece: +const WALL_KICKS_I: Record<string, [number, number][]> = { + "0>1": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]], + "1>0": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]], + "1>2": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]], + "2>1": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]], + "2>3": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]], + "3>2": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]], + "3>0": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]], + "0>3": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]], +}; + +// Scoring table (NES-style) +const LINE_SCORES: Record<number, number> = { + 1: 100, + 2: 300, + 3: 500, + 4: 800, +}; + +const PIECE_TYPES: PieceType[] = ["I", "O", "T", "S", "Z", "J", "L"]; + +// ----- DOM Setup ----- + +function createCell(className: string): HTMLDivElement { + const div = document.createElement("div"); + div.className = className; + return div; +} + +function buildBoardDOM(): HTMLDivElement { + const boardEl = document.getElementById("board") as HTMLDivElement; + boardEl.innerHTML = ""; + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + boardEl.appendChild(createCell("cell")); + } + } + return boardEl; +} + +function buildPreviewDOM(id: string): HTMLDivElement { + const el = document.getElementById(id) as HTMLDivElement; + el.innerHTML = ""; + for (let r = 0; r < 4; r++) { + for (let c = 0; c < 4; c++) { + el.appendChild(createCell("preview-cell")); + } + } + return el; +} + +// ----- Game State ----- + +let board: (PieceType | null)[][]; +let cells: HTMLDivElement[]; +let nextPreviewCells: HTMLDivElement[]; +let holdPreviewCells: HTMLDivElement[]; + +let currentPiece: ActivePiece | null; +let nextType: PieceType; +let holdType: PieceType | null; +let canHold: boolean; + +let score: number; +let level: number; +let linesCleared: number; +let combo: number; + +let gameOver: boolean; +let paused: boolean; +let started: boolean; +let clearing: boolean; + +let dropTimer: number; +let lastTime: number; +let animFrameId: number; + +// Bag randomizer for fair piece distribution +let bag: PieceType[] = []; + +// ----- Initialization ----- + +function initBoard(): void { + board = []; + for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) { + board.push(new Array<PieceType | null>(COLS).fill(null)); + } +} + +function initGame(): void { + initBoard(); + score = 0; + level = 0; + linesCleared = 0; + combo = -1; + gameOver = false; + paused = false; + clearing = false; + holdType = null; + canHold = true; + bag = []; + nextType = pullFromBag(); + currentPiece = null; + dropTimer = 0; + lastTime = 0; + spawnPiece(); +} + +// ----- Bag Randomizer ----- + +function fillBag(): void { + const b = [...PIECE_TYPES]; + // Fisher-Yates shuffle + for (let i = b.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [b[i], b[j]] = [b[j], b[i]]; + } + bag = b; +} + +function pullFromBag(): PieceType { + if (bag.length === 0) fillBag(); + return bag.pop()!; +} + +// ----- Piece Spawning ----- + +function spawnPiece(): void { + const type = nextType; + nextType = pullFromBag(); + canHold = true; + + const shape = SHAPES[type][0]; + // Spawn centered, in the hidden rows area + const x = Math.floor((COLS - shape[0].length) / 2); + const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area + + currentPiece = { type, shape, pos: { x, y }, rotation: 0 }; + + if (!isValid(shape, currentPiece.pos)) { + // Game over – can't spawn + gameOver = true; + currentPiece = null; + showOverlay("gameover"); + } + + renderPreview(nextPreviewCells, nextType); + renderHoldPreview(); +} + +// ----- Collision Detection ----- + +function isValid(shape: number[][], pos: Position): boolean { + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) continue; + const bx = pos.x + c; + const by = pos.y + r; + if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false; + if (by >= 0 && board[by][bx] !== null) return false; + } + } + return true; +} + +// ----- Movement ----- + +function movePiece(dx: number, dy: number): boolean { + if (!currentPiece) return false; + const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy }; + if (isValid(currentPiece.shape, newPos)) { + currentPiece.pos = newPos; + return true; + } + return false; +} + +function moveDown(): boolean { + if (!currentPiece) return false; + if (movePiece(0, 1)) return true; + lockPiece(); + return false; +} + +// ----- Rotation with Wall Kicks (SRS) ----- + +function rotatePiece(dir: 1 | -1): void { + if (!currentPiece) return; + const oldRot = currentPiece.rotation; + const newRot = (oldRot + dir + 4) % 4; + const newShape = SHAPES[currentPiece.type][newRot]; + + const kickKey = `${oldRot}>${newRot}`; + const kicks = currentPiece.type === "I" + ? (WALL_KICKS_I[kickKey] || [[0, 0]]) + : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]); + + for (const [kx, ky] of kicks) { + const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky }; + if (isValid(newShape, testPos)) { + currentPiece.shape = newShape; + currentPiece.pos = testPos; + currentPiece.rotation = newRot; + return; + } + } + // Rotation failed – no valid kick +} + +// ----- Hard Drop ----- + +function hardDrop(): void { + if (!currentPiece) return; + let dropped = 0; + while (movePiece(0, 1)) { + dropped++; + } + score += dropped * 2; + lockPiece(); +} + +// ----- Ghost Piece Position ----- + +function getGhostY(): number { + if (!currentPiece) return 0; + let ghostY = currentPiece.pos.y; + const testPos = { x: currentPiece.pos.x, y: ghostY }; + while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) { + testPos.y++; + } + return testPos.y; +} + +// ----- Lock Piece & Line Clear ----- + +function lockPiece(): void { + if (!currentPiece) return; + const { shape, pos, type } = currentPiece; + + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) continue; + const by = pos.y + r; + const bx = pos.x + c; + if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) { + board[by][bx] = type; + } + } + } + + currentPiece = null; + + // Check for completed lines + const clearedRows: number[] = []; + for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) { + if (board[r].every((cell) => cell !== null)) { + clearedRows.push(r); + } + } + + if (clearedRows.length > 0) { + combo++; + clearing = true; + // Animate then remove + animateClear(clearedRows, () => { + removeLines(clearedRows); + const n = clearedRows.length; + const baseScore = LINE_SCORES[n] || 0; + score += baseScore * (level + 1); + // Combo bonus + if (combo > 0) { + score += 50 * combo * (level + 1); + } + linesCleared += n; + level = Math.floor(linesCleared / 10); + updateStats(); + clearing = false; + spawnPiece(); + }); + } else { + combo = -1; + updateStats(); + spawnPiece(); + } +} + +function animateClear(rows: number[], callback: () => void): void { + // Mark cells as clearing + for (const r of rows) { + for (let c = 0; c < COLS; c++) { + const boardRow = r - HIDDEN_ROWS; + if (boardRow >= 0 && boardRow < ROWS) { + const idx = boardRow * COLS + c; + cells[idx].classList.add("clearing"); + } + } + } + setTimeout(callback, 400); +} + +function removeLines(rows: number[]): void { + // Sort descending so we remove from bottom up + rows.sort((a, b) => b - a); + for (const r of rows) { + board.splice(r, 1); + board.unshift(new Array<PieceType | null>(COLS).fill(null)); + } +} + +// ----- Hold ----- + +function holdPiece(): void { + if (!currentPiece || !canHold) return; + canHold = false; + const type = currentPiece.type; + if (holdType !== null) { + const prevHold = holdType; + holdType = type; + // Spawn the held piece + currentPiece = null; + const shape = SHAPES[prevHold][0]; + const x = Math.floor((COLS - shape[0].length) / 2); + const y = HIDDEN_ROWS - shape.length; + currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 }; + } else { + holdType = type; + currentPiece = null; + spawnPiece(); + } + renderHoldPreview(); +} + +// ----- Rendering ----- + +function render(): void { + // Clear all cells + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + const idx = r * COLS + c; + const cell = cells[idx]; + const boardR = r + HIDDEN_ROWS; + // Remove dynamic classes + cell.className = "cell"; + const value = board[boardR][c]; + if (value) { + cell.classList.add("filled", value); + } + } + } + + // Render ghost piece + if (currentPiece) { + const ghostY = getGhostY(); + if (ghostY !== currentPiece.pos.y) { + renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, "ghost", currentPiece.type); + } + // Render current piece + renderShape(currentPiece.shape, currentPiece.pos, "filled", currentPiece.type); + } +} + +function renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void { + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) continue; + const by = pos.y + r; + const bx = pos.x + c; + const boardRow = by - HIDDEN_ROWS; + if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) { + const idx = boardRow * COLS + bx; + cells[idx].className = `cell ${cls} ${cls === "ghost" ? "" : type}`.trim(); + if (cls === "ghost") { + cells[idx].className = "cell ghost"; + } + } + } + } +} + +function renderPreview(previewCells: HTMLDivElement[], type: PieceType): void { + const shape = SHAPES[type][0]; + // Center the shape in the 4×4 preview + const offR = Math.floor((4 - shape.length) / 2); + const offC = Math.floor((4 - shape[0].length) / 2); + for (let r = 0; r < 4; r++) { + for (let c = 0; c < 4; c++) { + const idx = r * 4 + c; + previewCells[idx].className = "preview-cell"; + const sr = r - offR; + const sc = c - offC; + if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) { + previewCells[idx].classList.add(type); + } + } + } +} + +function renderHoldPreview(): void { + if (holdType) { + renderPreview(holdPreviewCells, holdType); + } else { + for (const cell of holdPreviewCells) { + cell.className = "preview-cell"; + } + } +} + +function updateStats(): void { + document.getElementById("score-value")!.textContent = score.toLocaleString(); + document.getElementById("level-value")!.textContent = String(level + 1); + document.getElementById("lines-value")!.textContent = String(linesCleared); +} + +// ----- Drop Speed ----- + +function getDropInterval(): number { + // Speed curve (milliseconds per drop), gets faster each level + // Based roughly on NES Tetris speed curve + const speeds = [ + 800, 720, 630, 550, 470, 380, 300, 220, 150, 100, + 80, 70, 60, 50, 40, 30, 20, 15, 10, 8, + ]; + if (level < speeds.length) return speeds[level]; + return 5; +} + +// ----- Game Loop ----- + +function gameLoop(timestamp: number): void { + if (gameOver || paused || !started) return; + + if (clearing) { + animFrameId = requestAnimationFrame(gameLoop); + return; + } + + if (lastTime === 0) lastTime = timestamp; + const delta = timestamp - lastTime; + lastTime = timestamp; + + updateDAS(delta); + + dropTimer += delta; + const interval = getDropInterval(); + + if (dropTimer >= interval) { + dropTimer -= interval; + moveDown(); + } + + render(); + animFrameId = requestAnimationFrame(gameLoop); +} + +// ----- Input Handling ----- + +// DAS (Delayed Auto Shift) state +let dasKey: string | null = null; +let dasTimer: number = 0; +let dasActive: boolean = false; +const DAS_DELAY = 170; // ms before auto-repeat starts +const DAS_RATE = 50; // ms between auto-repeats + +function handleKeyDown(e: KeyboardEvent): void { + if (!started || gameOver) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + startGame(); + } + return; + } + + if (e.key === "p" || e.key === "P" || e.key === "Escape") { + togglePause(); + return; + } + + if (paused) return; + + switch (e.key) { + case "ArrowLeft": + e.preventDefault(); + if (!dasActive) movePiece(-1, 0); + dasKey = "left"; + dasTimer = 0; + dasActive = false; + break; + case "ArrowRight": + e.preventDefault(); + if (!dasActive) movePiece(1, 0); + dasKey = "right"; + dasTimer = 0; + dasActive = false; + break; + case "ArrowDown": + e.preventDefault(); + if (moveDown()) { + score += 1; + updateStats(); + } + break; + case "ArrowUp": + case "x": + case "X": + e.preventDefault(); + rotatePiece(1); + break; + case "z": + case "Z": + e.preventDefault(); + rotatePiece(-1); + break; + case " ": + e.preventDefault(); + hardDrop(); + break; + case "c": + case "C": + case "Shift": + e.preventDefault(); + holdPiece(); + break; + } + + render(); +} + +function handleKeyUp(e: KeyboardEvent): void { + if (e.key === "ArrowLeft" && dasKey === "left") dasKey = null; + if (e.key === "ArrowRight" && dasKey === "right") dasKey = null; +} + +function updateDAS(delta: number): void { + if (dasKey === null) return; + dasTimer += delta; + if (!dasActive && dasTimer >= DAS_DELAY) { + dasActive = true; + dasTimer = 0; + } + if (dasActive && dasTimer >= DAS_RATE) { + dasTimer -= DAS_RATE; + if (dasKey === "left") movePiece(-1, 0); + else if (dasKey === "right") movePiece(1, 0); + render(); + } +} + +// ----- Pause ----- + +function togglePause(): void { + paused = !paused; + const overlay = document.getElementById("pause-overlay")!; + if (paused) { + overlay.classList.remove("hidden"); + } else { + overlay.classList.add("hidden"); + lastTime = 0; + animFrameId = requestAnimationFrame(gameLoop); + } +} + +// ----- Overlay Management ----- + +function showOverlay(which: "start" | "gameover"): void { + const startOverlay = document.getElementById("start-overlay")!; + const gameoverOverlay = document.getElementById("gameover-overlay")!; + + startOverlay.classList.add("hidden"); + gameoverOverlay.classList.add("hidden"); + + if (which === "start") { + startOverlay.classList.remove("hidden"); + } else { + document.getElementById("final-score")!.textContent = score.toLocaleString(); + gameoverOverlay.classList.remove("hidden"); + } +} + +function hideAllOverlays(): void { + document.getElementById("start-overlay")!.classList.add("hidden"); + document.getElementById("gameover-overlay")!.classList.add("hidden"); + document.getElementById("pause-overlay")!.classList.add("hidden"); +} + +// ----- Start / Restart ----- + +function startGame(): void { + hideAllOverlays(); + initGame(); + started = true; + lastTime = 0; + dropTimer = 0; + updateStats(); + render(); + animFrameId = requestAnimationFrame(gameLoop); +} + +// ----- Boot ----- + +function boot(): void { + const boardEl = buildBoardDOM(); + cells = Array.from(boardEl.querySelectorAll(".cell")) as HTMLDivElement[]; + buildPreviewDOM("next-preview"); + buildPreviewDOM("hold-preview"); + nextPreviewCells = Array.from( + (document.getElementById("next-preview") as HTMLDivElement).querySelectorAll(".preview-cell") + ) as HTMLDivElement[]; + holdPreviewCells = Array.from( + (document.getElementById("hold-preview") as HTMLDivElement).querySelectorAll(".preview-cell") + ) as HTMLDivElement[]; + + document.addEventListener("keydown", handleKeyDown); + document.addEventListener("keyup", handleKeyUp); + + initBoard(); + showOverlay("start"); +} + +document.addEventListener("DOMContentLoaded", boot); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 30_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/tetris.spec.ts @@ -0,0 +1,96 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + }); + + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Give the page a moment to finish initializing + await page.waitForTimeout(2000); + + expect(errors).toEqual([]); + }); + + test("game board is visible", async ({ page }) => { + // A Tetris game should render either a <canvas> or a grid of DOM elements + const canvas = page.locator("canvas"); + const gridContainer = page.locator( + [ + '[class*="board"]', + '[class*="grid"]', + '[class*="game"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); + + const canvasCount = await canvas.count(); + const gridCount = await gridContainer.count(); + + expect( + canvasCount + gridCount, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + }); + + test("keyboard input does not crash the game", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Press every key a Tetris game should handle + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + for (const key of keys) { + await page.keyboard.press(key); + await page.waitForTimeout(150); + } + + expect(errors).toEqual([]); + }); + + test("game state changes over time", async ({ page }) => { + // If the game is running, the visual output should change as pieces fall + const shot1 = await page.screenshot(); + await page.waitForTimeout(3000); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the page to visually change over 3 seconds (pieces should be falling)" + ).toBe(false); + }); +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 60_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/tetris.spec.ts @@ -0,0 +1,474 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +// Find the game surface: canvas or a grid-like DOM container. +function gameBoard(page: Page) { + return page.locator( + [ + "canvas", + '[class*="board"]', + '[class*="grid"]', + '[class*="game-area"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); +} + +// Click the board area to make sure it has focus, then try common +// start interactions in case the game waits for user action. +async function ensureGameStarted(page: Page) { + const board = gameBoard(page); + const count = await board.count(); + if (count > 0) { + try { + await board.first().click({ timeout: 2000 }); + } catch { + // click failed, continue anyway + } + } + + // Some games need a key press or button click to start + const startButton = page.locator( + 'button:has-text("start"), button:has-text("Start"), button:has-text("play"), button:has-text("Play"), [class*="start"], [id*="start"]' + ); + if ((await startButton.count()) > 0) { + try { + await startButton.first().click({ timeout: 2000 }); + } catch { + // ignore + } + } + + // Press Enter/Space as a fallback start trigger + await page.keyboard.press("Enter"); + await page.waitForTimeout(300); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + await page.waitForTimeout(1000); + await ensureGameStarted(page); + }); + + // ---- 1. Page loads without errors ---- + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + await page.waitForTimeout(2000); + expect(errors).toEqual([]); + }); + + // ---- 2. Game board is visible ---- + test("game board is visible", async ({ page }) => { + const board = gameBoard(page); + const count = await board.count(); + + expect( + count, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + + // The board element should have meaningful dimensions + const box = await board.first().boundingBox(); + expect(box, "Game board should be visible on screen").not.toBeNull(); + expect(box!.width).toBeGreaterThan(50); + expect(box!.height).toBeGreaterThan(50); + }); + + // ---- 3. Game starts automatically or via interaction ---- + test("game starts", async ({ page }) => { + // After beforeEach, the game should be running. Verify by checking that + // the page is not static: take two screenshots separated by time. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the game to show visual activity after starting" + ).toBe(false); + }); + + // ---- 4. Piece falls automatically (auto-drop) ---- + test("piece falls automatically", async ({ page }) => { + // Take screenshots at intervals without pressing any keys. + // A falling piece should cause visual changes. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot3 = await page.screenshot(); + + const buf1 = Buffer.from(shot1); + const buf2 = Buffer.from(shot2); + const buf3 = Buffer.from(shot3); + + // At least one pair should differ (piece is moving down) + const changed = !buf1.equals(buf2) || !buf2.equals(buf3); + expect(changed, "Expected piece to fall over time without input").toBe( + true + ); + }); + + // ---- 5. Left arrow moves piece left ---- + test("left arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + // The piece should have moved, so the screenshots should differ + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing left arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 6. Right arrow moves piece right ---- + test("right arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing right arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 7. Down arrow moves piece down faster ---- + test("down arrow accelerates piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + for (let i = 0; i < 10; i++) { + await page.keyboard.press("ArrowDown"); + await page.waitForTimeout(50); + } + await page.waitForTimeout(200); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing down arrow repeatedly" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 8. Up arrow (or Z) rotates piece ---- + test("rotation changes the piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing rotate key" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 9. Space bar hard-drops piece ---- + test("space bar hard-drops piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing space (hard drop)" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 10. Pieces lock at the bottom ---- + test("pieces lock at the bottom", async ({ page }) => { + // Hard-drop several pieces and check that the bottom of the board + // accumulates filled cells (the visual should change cumulatively). + const shots: Buffer[] = []; + + shots.push(Buffer.from(await page.screenshot())); + + for (let i = 0; i < 3; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(800); + } + + shots.push(Buffer.from(await page.screenshot())); + + // After 3 hard drops, the board should look different from the start + // because pieces have stacked up at the bottom. + expect( + shots[0].equals(shots[1]), + "Expected pieces to stack up at the bottom after hard drops" + ).toBe(false); + }); + + // ---- 11. New piece spawns after lock ---- + test("new piece spawns after locking", async ({ page }) => { + // Hard-drop to lock a piece, then wait and verify the game is still + // showing activity (a new piece should be falling). + await page.keyboard.press("Space"); + await page.waitForTimeout(1000); + + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + + // If a new piece spawned and is falling, the screen should change + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected a new piece to spawn and fall after the previous one locked" + ).toBe(false); + }); + + // ---- 12. Multiple different pieces appear ---- + test("multiple different pieces appear", async ({ page }) => { + // Play through several pieces and capture screenshots. Different piece + // shapes should produce visually distinct patterns. + const shots: Buffer[] = []; + + for (let i = 0; i < 6; i++) { + // Move each piece to a different column so they don't overlap identically + if (i % 2 === 0) { + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + } else { + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(600); + shots.push(Buffer.from(await page.screenshot())); + } + + // At least some consecutive screenshots should differ (different piece shapes) + let differences = 0; + for (let i = 1; i < shots.length; i++) { + if (!shots[i - 1].equals(shots[i])) differences++; + } + + expect( + differences, + "Expected to see visual differences between consecutive pieces (different shapes)" + ).toBeGreaterThanOrEqual(2); + }); + + // ---- 13. Completed line clears ---- + test("completed line clears", async ({ page }) => { + // Fill a row by dropping many pieces. Observe whether any row disappears. + // We can detect this by tracking the total filled area -- after a line clear, + // the board should have less filled content than just before the clear. + const pageText = async () => + (await page.evaluate(() => document.body.innerText)) || ""; + + // Drop many pieces rapidly to fill rows + for (let i = 0; i < 30; i++) { + // Vary positions to try to complete a row + const moves = (i % 5) - 2; // -2, -1, 0, 1, 2 + for (let m = 0; m < Math.abs(moves); m++) { + await page.keyboard.press( + moves < 0 ? "ArrowLeft" : "ArrowRight" + ); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + // Check if a score or lines counter changed (common indicators of line clears) + const text = await pageText(); + const numbers = (text.match(/\d+/g) || []).map(Number); + const hasNonZero = numbers.some((n) => n > 0); + + // Also check visual: the board should not be completely static + const shot1 = await page.screenshot(); + await page.waitForTimeout(1000); + const shot2 = await page.screenshot(); + + // Either: score/lines increased, or game is still active (meaning lines cleared + // and made room for more pieces instead of game over) + const stillActive = !Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + hasNonZero || stillActive, + "Expected evidence of line clearing (score > 0 or game still active after many drops)" + ).toBe(true); + }); + + // ---- 14. Score increases during play ---- + test("score increases during play", async ({ page }) => { + // Look for a score display on the page + const getNumbers = async () => { + const text = (await page.evaluate(() => document.body.innerText)) || ""; + return (text.match(/\d+/g) || []).map(Number); + }; + + const numbersBefore = await getNumbers(); + + // Play for a while: drop several pieces + for (let i = 0; i < 15; i++) { + const offset = (i % 5) - 2; + for (let m = 0; m < Math.abs(offset); m++) { + await page.keyboard.press(offset < 0 ? "ArrowLeft" : "ArrowRight"); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + const numbersAfter = await getNumbers(); + + // At least one number on the page should have increased + // (score, lines counter, level, etc.) + let anyIncreased = false; + const maxLen = Math.min(numbersBefore.length, numbersAfter.length); + for (let i = 0; i < maxLen; i++) { + if (numbersAfter[i] > numbersBefore[i]) { + anyIncreased = true; + break; + } + } + + // Also accept if new numbers appeared + if (!anyIncreased && numbersAfter.length > numbersBefore.length) { + anyIncreased = true; + } + + // Also accept if the max number increased + if (!anyIncreased) { + const maxBefore = numbersBefore.length > 0 ? Math.max(...numbersBefore) : 0; + const maxAfter = numbersAfter.length > 0 ? Math.max(...numbersAfter) : 0; + if (maxAfter > maxBefore) anyIncreased = true; + } + + expect( + anyIncreased, + "Expected at least one numeric value on the page to increase during play (score, lines, level)" + ).toBe(true); + }); + + // ---- 15. Game over when pieces reach top ---- + test("game over when pieces reach top", async ({ page }) => { + // Stack pieces in the center until the game ends. + // Drop as many pieces as possible straight down. + for (let i = 0; i < 50; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(200); + } + + await page.waitForTimeout(2000); + + // After stacking to overflow, the game should show some game-over indicator: + // - text saying "game over", "you lose", "try again", "restart", "end" + // - or the game stops updating (static screen) + const text = ((await page.evaluate(() => document.body.innerText)) || "").toLowerCase(); + const gameOverText = + text.includes("game over") || + text.includes("gameover") || + text.includes("you lose") || + text.includes("try again") || + text.includes("restart") || + text.includes("play again") || + text.includes("ended"); + + // Check if the game stopped (screen is static) + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + const isStatic = Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + gameOverText || isStatic, + "Expected game-over text or the game to stop after stacking pieces to the top" + ).toBe(true); + }); + + // ---- 16. Game runs for 30 seconds without crashing ---- + test("game runs for 30 seconds without crashing", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Simulate varied gameplay for 30 seconds + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + const start = Date.now(); + + while (Date.now() - start < 30_000) { + const key = keys[Math.floor(Math.random() * keys.length)]; + await page.keyboard.press(key); + await page.waitForTimeout(150 + Math.random() * 200); + } + + // The page should still be alive (not crashed, not blank) + const text = await page.evaluate(() => document.body.innerText); + expect(text, "Page body should not be empty after 30s of play").toBeTruthy(); + expect( + errors.length, + `Expected no console errors during 30s of play, got: ${errors.join("; ")}` + ).toBe(0); + }); +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tsconfig.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "none", + "outDir": "src", + "sourceMap": false, + "strict": true, + "noImplicitAny": true, + "removeComments": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": ["src/tetris.ts"], + "exclude": ["node_modules"] +} +\ No newline at end of file diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/package-lock.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/package-lock.json @@ -0,0 +1,2270 @@ +{ + "name": "loop-bench-l7ah2axi", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "loop-bench-l7ah2axi", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.4.tgz", + "integrity": "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.4", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.4.tgz", + "integrity": "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", + "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", + "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", + "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jscpd/badge-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/badge-reporter/-/badge-reporter-4.0.4.tgz", + "integrity": "sha512-I9b4MmLXPM2vo0SxSUWnNGKcA4PjQlD3GzXvFK60z43cN/EIdLbOq3FVwCL+dg2obUqGXKIzAm7EsDFTg0D+mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "badgen": "^3.2.3", + "colors": "^1.4.0", + "fs-extra": "^11.2.0" + } + }, + "node_modules/@jscpd/core": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/core/-/core-4.0.4.tgz", + "integrity": "sha512-QGMT3iXEX1fI6lgjPH+x8eyJwhwr2KkpSF5uBpjC0Z5Xloj0yFTFLtwJT+RhxP/Ob4WYrtx2jvpKB269oIwgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/@jscpd/finder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/finder/-/finder-4.0.4.tgz", + "integrity": "sha512-qVUWY7Nzuvfd5OIk+n7/5CM98LmFroLqblRXAI2gDABwZrc7qS+WH2SNr0qoUq0f4OqwM+piiwKvwL/VDNn/Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "blamer": "^1.0.6", + "bytes": "^3.1.2", + "cli-table3": "^0.6.5", + "colors": "^1.4.0", + "fast-glob": "^3.3.2", + "fs-extra": "^11.2.0", + "markdown-table": "^2.0.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/html-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/html-reporter/-/html-reporter-4.0.4.tgz", + "integrity": "sha512-YiepyeYkeH74Kx59PJRdUdonznct0wHPFkf6FLQN+mCBoy6leAWCcOfHtcexnp+UsBFDlItG5nRdKrDSxSH+Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "1.4.0", + "fs-extra": "^11.2.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/tokenizer": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/tokenizer/-/tokenizer-4.0.4.tgz", + "integrity": "sha512-xxYYY/qaLah/FlwogEbGIxx9CjDO+G9E6qawcy26WwrflzJb6wsnhjwdneN6Wb0RNCDsqvzY+bzG453jsin4UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "reprism": "^0.0.11", + "spark-md5": "^3.0.2" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/badgen": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/badgen/-/badgen-3.2.3.tgz", + "integrity": "sha512-svDuwkc63E/z0ky3drpUppB83s/nlgDciH9m+STwwQoWyq7yCgew1qEfJ+9axkKdNq7MskByptWUN9j1PGMwFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/blamer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/blamer/-/blamer-1.0.7.tgz", + "integrity": "sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^4.0.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gitignore-to-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitignore-to-glob/-/gitignore-to-glob-0.3.0.tgz", + "integrity": "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.4 <5 || >=6.9" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jscpd": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-4.0.8.tgz", + "integrity": "sha512-d2VNT/2Hv4dxT2/59He8Lyda4DYOxPRyRG9zBaOpTZAqJCVf2xLrBlZkT8Va6Lo9u3X2qz8Bpq4HrDi4JsrQhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/badge-reporter": "4.0.4", + "@jscpd/core": "4.0.4", + "@jscpd/finder": "4.0.4", + "@jscpd/html-reporter": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "colors": "^1.4.0", + "commander": "^5.0.0", + "fs-extra": "^11.2.0", + "gitignore-to-glob": "^0.3.0", + "jscpd-sarif-reporter": "4.0.6" + }, + "bin": { + "jscpd": "bin/jscpd" + } + }, + "node_modules/jscpd-sarif-reporter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/jscpd-sarif-reporter/-/jscpd-sarif-reporter-4.0.6.tgz", + "integrity": "sha512-b9Sm3IPZ3+m8Lwa4gZa+4/LhDhlc/ZLEsLXKSOy1DANQ6kx0ueqZT+fUHWEdQ6m0o3+RIVIa7DmvLSojQD05ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "fs-extra": "^11.2.0", + "node-sarif-builder": "^3.4.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/pug": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", + "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.4", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", + "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/reprism": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/reprism/-/reprism-0.0.11.tgz", + "integrity": "sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/package.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/package.json @@ -0,0 +1,20 @@ +{ + "name": "loop-bench-l7ah2axi", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-few/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-few/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 30_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-few/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-few/tetris.spec.ts @@ -0,0 +1,96 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + }); + + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Give the page a moment to finish initializing + await page.waitForTimeout(2000); + + expect(errors).toEqual([]); + }); + + test("game board is visible", async ({ page }) => { + // A Tetris game should render either a <canvas> or a grid of DOM elements + const canvas = page.locator("canvas"); + const gridContainer = page.locator( + [ + '[class*="board"]', + '[class*="grid"]', + '[class*="game"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); + + const canvasCount = await canvas.count(); + const gridCount = await gridContainer.count(); + + expect( + canvasCount + gridCount, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + }); + + test("keyboard input does not crash the game", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Press every key a Tetris game should handle + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + for (const key of keys) { + await page.keyboard.press(key); + await page.waitForTimeout(150); + } + + expect(errors).toEqual([]); + }); + + test("game state changes over time", async ({ page }) => { + // If the game is running, the visual output should change as pieces fall + const shot1 = await page.screenshot(); + await page.waitForTimeout(3000); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the page to visually change over 3 seconds (pieces should be falling)" + ).toBe(false); + }); +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-full/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-full/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 60_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-full/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/tests-full/tetris.spec.ts @@ -0,0 +1,474 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +// Find the game surface: canvas or a grid-like DOM container. +function gameBoard(page: Page) { + return page.locator( + [ + "canvas", + '[class*="board"]', + '[class*="grid"]', + '[class*="game-area"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); +} + +// Click the board area to make sure it has focus, then try common +// start interactions in case the game waits for user action. +async function ensureGameStarted(page: Page) { + const board = gameBoard(page); + const count = await board.count(); + if (count > 0) { + try { + await board.first().click({ timeout: 2000 }); + } catch { + // click failed, continue anyway + } + } + + // Some games need a key press or button click to start + const startButton = page.locator( + 'button:has-text("start"), button:has-text("Start"), button:has-text("play"), button:has-text("Play"), [class*="start"], [id*="start"]' + ); + if ((await startButton.count()) > 0) { + try { + await startButton.first().click({ timeout: 2000 }); + } catch { + // ignore + } + } + + // Press Enter/Space as a fallback start trigger + await page.keyboard.press("Enter"); + await page.waitForTimeout(300); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + await page.waitForTimeout(1000); + await ensureGameStarted(page); + }); + + // ---- 1. Page loads without errors ---- + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + await page.waitForTimeout(2000); + expect(errors).toEqual([]); + }); + + // ---- 2. Game board is visible ---- + test("game board is visible", async ({ page }) => { + const board = gameBoard(page); + const count = await board.count(); + + expect( + count, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + + // The board element should have meaningful dimensions + const box = await board.first().boundingBox(); + expect(box, "Game board should be visible on screen").not.toBeNull(); + expect(box!.width).toBeGreaterThan(50); + expect(box!.height).toBeGreaterThan(50); + }); + + // ---- 3. Game starts automatically or via interaction ---- + test("game starts", async ({ page }) => { + // After beforeEach, the game should be running. Verify by checking that + // the page is not static: take two screenshots separated by time. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the game to show visual activity after starting" + ).toBe(false); + }); + + // ---- 4. Piece falls automatically (auto-drop) ---- + test("piece falls automatically", async ({ page }) => { + // Take screenshots at intervals without pressing any keys. + // A falling piece should cause visual changes. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot3 = await page.screenshot(); + + const buf1 = Buffer.from(shot1); + const buf2 = Buffer.from(shot2); + const buf3 = Buffer.from(shot3); + + // At least one pair should differ (piece is moving down) + const changed = !buf1.equals(buf2) || !buf2.equals(buf3); + expect(changed, "Expected piece to fall over time without input").toBe( + true + ); + }); + + // ---- 5. Left arrow moves piece left ---- + test("left arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + // The piece should have moved, so the screenshots should differ + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing left arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 6. Right arrow moves piece right ---- + test("right arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing right arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 7. Down arrow moves piece down faster ---- + test("down arrow accelerates piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + for (let i = 0; i < 10; i++) { + await page.keyboard.press("ArrowDown"); + await page.waitForTimeout(50); + } + await page.waitForTimeout(200); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing down arrow repeatedly" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 8. Up arrow (or Z) rotates piece ---- + test("rotation changes the piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing rotate key" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 9. Space bar hard-drops piece ---- + test("space bar hard-drops piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing space (hard drop)" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 10. Pieces lock at the bottom ---- + test("pieces lock at the bottom", async ({ page }) => { + // Hard-drop several pieces and check that the bottom of the board + // accumulates filled cells (the visual should change cumulatively). + const shots: Buffer[] = []; + + shots.push(Buffer.from(await page.screenshot())); + + for (let i = 0; i < 3; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(800); + } + + shots.push(Buffer.from(await page.screenshot())); + + // After 3 hard drops, the board should look different from the start + // because pieces have stacked up at the bottom. + expect( + shots[0].equals(shots[1]), + "Expected pieces to stack up at the bottom after hard drops" + ).toBe(false); + }); + + // ---- 11. New piece spawns after lock ---- + test("new piece spawns after locking", async ({ page }) => { + // Hard-drop to lock a piece, then wait and verify the game is still + // showing activity (a new piece should be falling). + await page.keyboard.press("Space"); + await page.waitForTimeout(1000); + + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + + // If a new piece spawned and is falling, the screen should change + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected a new piece to spawn and fall after the previous one locked" + ).toBe(false); + }); + + // ---- 12. Multiple different pieces appear ---- + test("multiple different pieces appear", async ({ page }) => { + // Play through several pieces and capture screenshots. Different piece + // shapes should produce visually distinct patterns. + const shots: Buffer[] = []; + + for (let i = 0; i < 6; i++) { + // Move each piece to a different column so they don't overlap identically + if (i % 2 === 0) { + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + } else { + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(600); + shots.push(Buffer.from(await page.screenshot())); + } + + // At least some consecutive screenshots should differ (different piece shapes) + let differences = 0; + for (let i = 1; i < shots.length; i++) { + if (!shots[i - 1].equals(shots[i])) differences++; + } + + expect( + differences, + "Expected to see visual differences between consecutive pieces (different shapes)" + ).toBeGreaterThanOrEqual(2); + }); + + // ---- 13. Completed line clears ---- + test("completed line clears", async ({ page }) => { + // Fill a row by dropping many pieces. Observe whether any row disappears. + // We can detect this by tracking the total filled area -- after a line clear, + // the board should have less filled content than just before the clear. + const pageText = async () => + (await page.evaluate(() => document.body.innerText)) || ""; + + // Drop many pieces rapidly to fill rows + for (let i = 0; i < 30; i++) { + // Vary positions to try to complete a row + const moves = (i % 5) - 2; // -2, -1, 0, 1, 2 + for (let m = 0; m < Math.abs(moves); m++) { + await page.keyboard.press( + moves < 0 ? "ArrowLeft" : "ArrowRight" + ); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + // Check if a score or lines counter changed (common indicators of line clears) + const text = await pageText(); + const numbers = (text.match(/\d+/g) || []).map(Number); + const hasNonZero = numbers.some((n) => n > 0); + + // Also check visual: the board should not be completely static + const shot1 = await page.screenshot(); + await page.waitForTimeout(1000); + const shot2 = await page.screenshot(); + + // Either: score/lines increased, or game is still active (meaning lines cleared + // and made room for more pieces instead of game over) + const stillActive = !Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + hasNonZero || stillActive, + "Expected evidence of line clearing (score > 0 or game still active after many drops)" + ).toBe(true); + }); + + // ---- 14. Score increases during play ---- + test("score increases during play", async ({ page }) => { + // Look for a score display on the page + const getNumbers = async () => { + const text = (await page.evaluate(() => document.body.innerText)) || ""; + return (text.match(/\d+/g) || []).map(Number); + }; + + const numbersBefore = await getNumbers(); + + // Play for a while: drop several pieces + for (let i = 0; i < 15; i++) { + const offset = (i % 5) - 2; + for (let m = 0; m < Math.abs(offset); m++) { + await page.keyboard.press(offset < 0 ? "ArrowLeft" : "ArrowRight"); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + const numbersAfter = await getNumbers(); + + // At least one number on the page should have increased + // (score, lines counter, level, etc.) + let anyIncreased = false; + const maxLen = Math.min(numbersBefore.length, numbersAfter.length); + for (let i = 0; i < maxLen; i++) { + if (numbersAfter[i] > numbersBefore[i]) { + anyIncreased = true; + break; + } + } + + // Also accept if new numbers appeared + if (!anyIncreased && numbersAfter.length > numbersBefore.length) { + anyIncreased = true; + } + + // Also accept if the max number increased + if (!anyIncreased) { + const maxBefore = numbersBefore.length > 0 ? Math.max(...numbersBefore) : 0; + const maxAfter = numbersAfter.length > 0 ? Math.max(...numbersAfter) : 0; + if (maxAfter > maxBefore) anyIncreased = true; + } + + expect( + anyIncreased, + "Expected at least one numeric value on the page to increase during play (score, lines, level)" + ).toBe(true); + }); + + // ---- 15. Game over when pieces reach top ---- + test("game over when pieces reach top", async ({ page }) => { + // Stack pieces in the center until the game ends. + // Drop as many pieces as possible straight down. + for (let i = 0; i < 50; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(200); + } + + await page.waitForTimeout(2000); + + // After stacking to overflow, the game should show some game-over indicator: + // - text saying "game over", "you lose", "try again", "restart", "end" + // - or the game stops updating (static screen) + const text = ((await page.evaluate(() => document.body.innerText)) || "").toLowerCase(); + const gameOverText = + text.includes("game over") || + text.includes("gameover") || + text.includes("you lose") || + text.includes("try again") || + text.includes("restart") || + text.includes("play again") || + text.includes("ended"); + + // Check if the game stopped (screen is static) + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + const isStatic = Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + gameOverText || isStatic, + "Expected game-over text or the game to stop after stacking pieces to the top" + ).toBe(true); + }); + + // ---- 16. Game runs for 30 seconds without crashing ---- + test("game runs for 30 seconds without crashing", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Simulate varied gameplay for 30 seconds + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + const start = Date.now(); + + while (Date.now() - start < 30_000) { + const key = keys[Math.floor(Math.random() * keys.length)]; + await page.keyboard.press(key); + await page.waitForTimeout(150 + Math.random() * 200); + } + + // The page should still be alive (not crashed, not blank) + const text = await page.evaluate(() => document.body.innerText); + expect(text, "Page body should not be empty after 30s of play").toBeTruthy(); + expect( + errors.length, + `Expected no console errors during 30s of play, got: ${errors.join("; ")}` + ).toBe(0); + }); +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/build/src/tetris.js b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/build/src/tetris.js @@ -0,0 +1,516 @@ +"use strict"; +var Color; +(function (Color) { + Color[Color["Empty"] = 0] = "Empty"; + Color[Color["Cyan"] = 1] = "Cyan"; + Color[Color["Yellow"] = 2] = "Yellow"; + Color[Color["Purple"] = 3] = "Purple"; + Color[Color["Green"] = 4] = "Green"; + Color[Color["Red"] = 5] = "Red"; + Color[Color["Blue"] = 6] = "Blue"; + Color[Color["Orange"] = 7] = "Orange"; +})(Color || (Color = {})); +const COLS = 10; +const ROWS = 20; +const CELL = 30; +const BOARD_PAD = 20; +const BOARD_W = COLS * CELL; +const BOARD_H = ROWS * CELL; +const SIDEBAR_W = 200; +const CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD; +const CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD; +const PALETTE = { + [Color.Empty]: [0.08, 0.08, 0.12, 1], + [Color.Cyan]: [0.0, 0.9, 0.9, 1], + [Color.Yellow]: [0.95, 0.9, 0.1, 1], + [Color.Purple]: [0.7, 0.2, 0.9, 1], + [Color.Green]: [0.1, 0.9, 0.3, 1], + [Color.Red]: [0.9, 0.2, 0.2, 1], + [Color.Blue]: [0.2, 0.3, 0.9, 1], + [Color.Orange]: [0.95, 0.55, 0.1, 1], +}; +const SHAPES = [ + { shape: [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], color: Color.Cyan }, + { shape: [[1, 1], [1, 1]], color: Color.Yellow }, + { shape: [[0, 1, 0], [1, 1, 1], [0, 0, 0]], color: Color.Purple }, + { shape: [[0, 1, 1], [1, 1, 0], [0, 0, 0]], color: Color.Green }, + { shape: [[1, 1, 0], [0, 1, 1], [0, 0, 0]], color: Color.Red }, + { shape: [[1, 0, 0], [1, 1, 1], [0, 0, 0]], color: Color.Blue }, + { shape: [[0, 0, 1], [1, 1, 1], [0, 0, 0]], color: Color.Orange }, +]; +const KICK_OFFSETS = [ + { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 1, y: 0 }, + { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 }, + { x: -2, y: 0 }, { x: 2, y: 0 }, +]; +let bag = []; +function refillBag() { + bag = [0, 1, 2, 3, 4, 5, 6]; + for (let i = bag.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [bag[i], bag[j]] = [bag[j], bag[i]]; + } +} +function nextIdx() { + if (bag.length === 0) + refillBag(); + return bag.pop(); +} +function makePiece(idx) { + const def = SHAPES[idx]; + const shape = def.shape.map(r => [...r]); + return { shape, color: def.color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} +function makePieceFrom(shape, color) { + return { shape: shape.map(r => [...r]), color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} +function rotateCW(shape) { + const n = shape.length; + const out = []; + for (let r = 0; r < n; r++) { + out.push([]); + for (let c = 0; c < n; c++) + out[r].push(shape[n - 1 - c][r]); + } + return out; +} +function createGrid() { + const g = []; + for (let r = 0; r < ROWS; r++) + g.push(new Array(COLS).fill(Color.Empty)); + return g; +} +function collides(grid, shape, pos) { + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const nx = pos.x + c, ny = pos.y + r; + if (nx < 0 || nx >= COLS || ny >= ROWS) + return true; + if (ny >= 0 && grid[ny][nx] !== Color.Empty) + return true; + } + return false; +} +function lock(grid, shape, pos, color) { + const g = grid.map(r => [...r]); + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const ny = pos.y + r, nx = pos.x + c; + if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) + g[ny][nx] = color; + } + return g; +} +function clearLines(grid) { + const remaining = grid.filter(row => row.some(c => c === Color.Empty)); + const cleared = ROWS - remaining.length; + while (remaining.length < ROWS) + remaining.unshift(new Array(COLS).fill(Color.Empty)); + return { grid: remaining, cleared }; +} +function ghostY(grid, shape, pos) { + let gy = pos.y; + while (!collides(grid, shape, { x: pos.x, y: gy + 1 })) + gy++; + return gy; +} +function calcScore(n, lvl) { + return [0, 40, 100, 300, 1200][n] * (lvl + 1); +} +function gravityMs(lvl) { + const s = [800, 720, 630, 550, 470, 380, 300, 220, 140, 100, 80, 80, 80, 70, 70, 70, 50, 50, 50, 30]; + return s[Math.min(lvl, s.length - 1)]; +} +const VERT = ` +attribute vec2 a_pos; +attribute vec4 a_col; +uniform vec2 u_res; +varying vec4 v_col; +void main() { + vec2 clip = (a_pos / u_res) * 2.0 - 1.0; + gl_Position = vec4(clip * vec2(1, -1), 0, 1); + v_col = a_col; +}`; +const FRAG = ` +precision mediump float; +varying vec4 v_col; +void main() { gl_FragColor = v_col; }`; +let gl; +let prog; +let aPos, aCol, uRes; +let buf; +function initGL(canvas) { + gl = canvas.getContext("webgl", { alpha: false }); + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0.06, 0.06, 0.1, 1); + function comp(type, src) { + const s = gl.createShader(type); + gl.shaderSource(s, src); + gl.compileShader(s); + return s; + } + const vs = comp(gl.VERTEX_SHADER, VERT); + const fs = comp(gl.FRAGMENT_SHADER, FRAG); + prog = gl.createProgram(); + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + gl.useProgram(prog); + aPos = gl.getAttribLocation(prog, "a_pos"); + aCol = gl.getAttribLocation(prog, "a_col"); + uRes = gl.getUniformLocation(prog, "u_res"); + buf = gl.createBuffer(); +} +function darker(c) { return [c[0] * 0.55, c[1] * 0.55, c[2] * 0.55, c[3]]; } +function lighter(c) { return [Math.min(1, c[0] * 1.35), Math.min(1, c[1] * 1.35), Math.min(1, c[2] * 1.35), c[3]]; } +function pushQuad(v, cl, x, y, w, h, c) { + v.push(x, y, x + w, y, x, y + h, x + w, y, x + w, y + h, x, y + h); + for (let i = 0; i < 6; i++) + cl.push(...c); +} +function pushBlock(v, cl, x, y, s, c) { + const b = 2; + pushQuad(v, cl, x, y, s, s, darker(c)); + pushQuad(v, cl, x + b, y + b, s - 2 * b, s - 2 * b, c); + pushQuad(v, cl, x + b, y + b, s - 2 * b, b, lighter(c)); + pushQuad(v, cl, x + b, y + b, b, s - 2 * b, lighter(c)); +} +let board; +let cur; +let nxt; +let held; +let canHold; +let score, level, linesCleared; +let gameOver, paused; +let lastTick; +let softDrop; +function resetGame() { + board = createGrid(); + cur = makePiece(nextIdx()); + nxt = makePiece(nextIdx()); + held = null; + canHold = true; + score = 0; + level = 0; + linesCleared = 0; + gameOver = false; + paused = false; + lastTick = performance.now(); + softDrop = false; + for (const k of Object.keys(keyTimers)) { + clearTimeout(keyTimers[k]); + clearInterval(keyTimers[k]); + } + keyTimers = {}; +} +function move(dx, dy) { + if (!cur) + return false; + const p = { x: cur.pos.x + dx, y: cur.pos.y + dy }; + if (!collides(board, cur.shape, p)) { + cur.pos = p; + return true; + } + return false; +} +function tryRotate() { + if (!cur) + return; + const rot = rotateCW(cur.shape); + for (const k of KICK_OFFSETS) { + const p = { x: cur.pos.x + k.x, y: cur.pos.y + k.y }; + if (!collides(board, rot, p)) { + cur.shape = rot; + cur.pos = p; + return; + } + } +} +function hardDrop() { + if (!cur) + return; + const gy = ghostY(board, cur.shape, cur.pos); + score += (gy - cur.pos.y) * 2; + cur.pos.y = gy; + lockAndAdvance(); +} +function lockAndAdvance() { + if (!cur) + return; + board = lock(board, cur.shape, cur.pos, cur.color); + const { grid: nb, cleared } = clearLines(board); + board = nb; + if (cleared) { + linesCleared += cleared; + score += calcScore(cleared, level); + level = Math.floor(linesCleared / 10); + } + cur = nxt; + nxt = makePiece(nextIdx()); + canHold = true; + if (cur && collides(board, cur.shape, cur.pos)) + gameOver = true; +} +function doHold() { + if (!cur || !canHold) + return; + canHold = false; + const cc = cur.color, cs = cur.shape.map(r => [...r]); + if (held) { + const hc = held.color, hs = held.shape; + cur = makePieceFrom(hs, hc); + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + } + else { + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + cur = nxt; + nxt = makePiece(nextIdx()); + } +} +let keyTimers = {}; +const DAS = 170, ARR = 50; +function startRepeat(code, fn) { + stopRepeat(code); + keyTimers[code + "_d"] = window.setTimeout(() => { keyTimers[code] = window.setInterval(fn, ARR); }, DAS); +} +function stopRepeat(code) { + clearTimeout(keyTimers[code + "_d"]); + clearInterval(keyTimers[code]); + delete keyTimers[code + "_d"]; + delete keyTimers[code]; +} +function onKeyDown(e) { + if (e.repeat) + return; + switch (e.code) { + case "ArrowLeft": + e.preventDefault(); + move(-1, 0); + startRepeat("ArrowLeft", () => move(-1, 0)); + break; + case "ArrowRight": + e.preventDefault(); + move(1, 0); + startRepeat("ArrowRight", () => move(1, 0)); + break; + case "ArrowDown": + e.preventDefault(); + softDrop = true; + break; + case "ArrowUp": + case "KeyX": + e.preventDefault(); + tryRotate(); + break; + case "KeyZ": + e.preventDefault(); + tryRotate(); + tryRotate(); + tryRotate(); + break; + case "Space": + e.preventDefault(); + hardDrop(); + break; + case "KeyC": + case "ShiftLeft": + case "ShiftRight": + e.preventDefault(); + doHold(); + break; + case "KeyP": + case "Escape": + paused = !paused; + break; + case "KeyR": + resetGame(); + break; + } +} +function onKeyUp(e) { + if (e.code === "ArrowLeft" || e.code === "ArrowRight") + stopRepeat(e.code); + if (e.code === "ArrowDown") + softDrop = false; +} +let canvas2d, ctx2d; +function drawFrame() { + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(uRes, CANVAS_W, CANVAS_H); + const v = [], cl = []; + pushQuad(v, cl, BOARD_PAD, BOARD_PAD, BOARD_W, BOARD_H, [0.05, 0.05, 0.08, 1]); + const gc = [0.12, 0.12, 0.18, 1]; + for (let c = 0; c <= COLS; c++) + pushQuad(v, cl, BOARD_PAD + c * CELL, BOARD_PAD, 1, BOARD_H, gc); + for (let r = 0; r <= ROWS; r++) + pushQuad(v, cl, BOARD_PAD, BOARD_PAD + r * CELL, BOARD_W, 1, gc); + for (let r = 0; r < ROWS; r++) + for (let c = 0; c < COLS; c++) + if (board[r][c] !== Color.Empty) + pushBlock(v, cl, BOARD_PAD + c * CELL, BOARD_PAD + r * CELL, CELL, PALETTE[board[r][c]]); + if (cur) { + const gy = ghostY(board, cur.shape, cur.pos); + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) + continue; + const py = gy + r; + if (py < 0) + continue; + pushQuad(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL + 1, BOARD_PAD + py * CELL + 1, CELL - 2, CELL - 2, [0.3, 0.3, 0.4, 0.35]); + } + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) + continue; + const py = cur.pos.y + r; + if (py < 0) + continue; + pushBlock(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL, BOARD_PAD + py * CELL, CELL, PALETTE[cur.color]); + } + } + const sx = BOARD_PAD + BOARD_W + BOARD_PAD; + const panelH = 130; + pushQuad(v, cl, sx, BOARD_PAD, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (nxt) { + const sz = nxt.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = BOARD_PAD + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (nxt.shape[r][c]) + pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[nxt.color]); + } + const holdY = BOARD_PAD + panelH + 15; + pushQuad(v, cl, sx, holdY, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (held) { + const sz = held.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = holdY + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (held.shape[r][c]) + pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[held.color]); + } + const stride = 6; + const data = new Float32Array(v.length + cl.length); + for (let i = 0, vi = 0, ci = 0; i < data.length; i += stride) { + data[i] = v[vi++]; + data[i + 1] = v[vi++]; + data[i + 2] = cl[ci++]; + data[i + 3] = cl[ci++]; + data[i + 4] = cl[ci++]; + data[i + 5] = cl[ci++]; + } + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); + const bs = stride * 4; + gl.enableVertexAttribArray(aPos); + gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, bs, 0); + gl.enableVertexAttribArray(aCol); + gl.vertexAttribPointer(aCol, 4, gl.FLOAT, false, bs, 8); + gl.drawArrays(gl.TRIANGLES, 0, data.length / stride); + const ctx = ctx2d; + ctx.clearRect(0, 0, CANVAS_W, CANVAS_H); + ctx.font = "bold 14px 'Segoe UI', Arial, sans-serif"; + ctx.fillStyle = "#aaa"; + ctx.textAlign = "left"; + ctx.fillText("NEXT", sx + 8, BOARD_PAD + 18); + ctx.fillText("HOLD", sx + 8, holdY + 18); + const iy = holdY + panelH + 30; + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("SCORE", sx + 8, iy); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(score.toLocaleString(), sx + 8, iy + 24); + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LEVEL", sx + 8, iy + 54); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(level + 1), sx + 8, iy + 78); + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LINES", sx + 8, iy + 108); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(linesCleared), sx + 8, iy + 132); + ctx.fillStyle = "#666"; + ctx.font = "12px 'Segoe UI', Arial, sans-serif"; + const help = ["← → Move", "↑ / X Rotate CW", "Z Rotate CCW", "↓ Soft Drop", "Space Hard Drop", "C Hold", "P Pause", "R Restart"]; + help.forEach((t, i) => ctx.fillText(t, sx + 8, iy + 170 + i * 18)); + if (gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 40, BOARD_W, 80); + ctx.textAlign = "center"; + ctx.fillStyle = "#ff4444"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("GAME OVER", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.fillStyle = "#ccc"; + ctx.font = "16px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("Press R to restart", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 28); + ctx.textAlign = "left"; + } + if (paused && !gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 30, BOARD_W, 60); + ctx.textAlign = "center"; + ctx.fillStyle = "#ffdd44"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("PAUSED", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.textAlign = "left"; + } +} +function tick(now) { + requestAnimationFrame(tick); + if (gameOver || paused) { + drawFrame(); + return; + } + const interval = softDrop ? Math.min(gravityMs(level), 50) : gravityMs(level); + if (now - lastTick >= interval) { + lastTick = now; + if (softDrop) + score += 1; + if (!move(0, 1)) + lockAndAdvance(); + } + drawFrame(); +} +function start() { + const wrapper = document.getElementById("game-wrapper"); + const canvas = document.createElement("canvas"); + canvas.width = CANVAS_W; + canvas.height = CANVAS_H; + canvas.style.display = "block"; + canvas.style.borderRadius = "4px"; + canvas.tabIndex = 0; + wrapper.appendChild(canvas); + canvas2d = document.createElement("canvas"); + canvas2d.width = CANVAS_W; + canvas2d.height = CANVAS_H; + canvas2d.style.position = "absolute"; + canvas2d.style.left = "0"; + canvas2d.style.top = "0"; + canvas2d.style.pointerEvents = "none"; + wrapper.style.position = "relative"; + wrapper.appendChild(canvas2d); + ctx2d = canvas2d.getContext("2d"); + initGL(canvas); + resetGame(); + canvas.addEventListener("keydown", onKeyDown); + canvas.addEventListener("keyup", onKeyUp); + canvas.focus(); + requestAnimationFrame(tick); +} +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start); +} +else { + start(); +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/build/tetris.js b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/build/tetris.js @@ -0,0 +1,516 @@ +"use strict"; +var Color; +(function (Color) { + Color[Color["Empty"] = 0] = "Empty"; + Color[Color["Cyan"] = 1] = "Cyan"; + Color[Color["Yellow"] = 2] = "Yellow"; + Color[Color["Purple"] = 3] = "Purple"; + Color[Color["Green"] = 4] = "Green"; + Color[Color["Red"] = 5] = "Red"; + Color[Color["Blue"] = 6] = "Blue"; + Color[Color["Orange"] = 7] = "Orange"; +})(Color || (Color = {})); +const COLS = 10; +const ROWS = 20; +const CELL = 30; +const BOARD_PAD = 20; +const BOARD_W = COLS * CELL; +const BOARD_H = ROWS * CELL; +const SIDEBAR_W = 200; +const CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD; +const CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD; +const PALETTE = { + [Color.Empty]: [0.08, 0.08, 0.12, 1], + [Color.Cyan]: [0.0, 0.9, 0.9, 1], + [Color.Yellow]: [0.95, 0.9, 0.1, 1], + [Color.Purple]: [0.7, 0.2, 0.9, 1], + [Color.Green]: [0.1, 0.9, 0.3, 1], + [Color.Red]: [0.9, 0.2, 0.2, 1], + [Color.Blue]: [0.2, 0.3, 0.9, 1], + [Color.Orange]: [0.95, 0.55, 0.1, 1], +}; +const SHAPES = [ + { shape: [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], color: Color.Cyan }, + { shape: [[1, 1], [1, 1]], color: Color.Yellow }, + { shape: [[0, 1, 0], [1, 1, 1], [0, 0, 0]], color: Color.Purple }, + { shape: [[0, 1, 1], [1, 1, 0], [0, 0, 0]], color: Color.Green }, + { shape: [[1, 1, 0], [0, 1, 1], [0, 0, 0]], color: Color.Red }, + { shape: [[1, 0, 0], [1, 1, 1], [0, 0, 0]], color: Color.Blue }, + { shape: [[0, 0, 1], [1, 1, 1], [0, 0, 0]], color: Color.Orange }, +]; +const KICK_OFFSETS = [ + { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 1, y: 0 }, + { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 }, + { x: -2, y: 0 }, { x: 2, y: 0 }, +]; +let bag = []; +function refillBag() { + bag = [0, 1, 2, 3, 4, 5, 6]; + for (let i = bag.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [bag[i], bag[j]] = [bag[j], bag[i]]; + } +} +function nextIdx() { + if (bag.length === 0) + refillBag(); + return bag.pop(); +} +function makePiece(idx) { + const def = SHAPES[idx]; + const shape = def.shape.map(r => [...r]); + return { shape, color: def.color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} +function makePieceFrom(shape, color) { + return { shape: shape.map(r => [...r]), color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} +function rotateCW(shape) { + const n = shape.length; + const out = []; + for (let r = 0; r < n; r++) { + out.push([]); + for (let c = 0; c < n; c++) + out[r].push(shape[n - 1 - c][r]); + } + return out; +} +function createGrid() { + const g = []; + for (let r = 0; r < ROWS; r++) + g.push(new Array(COLS).fill(Color.Empty)); + return g; +} +function collides(grid, shape, pos) { + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const nx = pos.x + c, ny = pos.y + r; + if (nx < 0 || nx >= COLS || ny >= ROWS) + return true; + if (ny >= 0 && grid[ny][nx] !== Color.Empty) + return true; + } + return false; +} +function lock(grid, shape, pos, color) { + const g = grid.map(r => [...r]); + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const ny = pos.y + r, nx = pos.x + c; + if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) + g[ny][nx] = color; + } + return g; +} +function clearLines(grid) { + const remaining = grid.filter(row => row.some(c => c === Color.Empty)); + const cleared = ROWS - remaining.length; + while (remaining.length < ROWS) + remaining.unshift(new Array(COLS).fill(Color.Empty)); + return { grid: remaining, cleared }; +} +function ghostY(grid, shape, pos) { + let gy = pos.y; + while (!collides(grid, shape, { x: pos.x, y: gy + 1 })) + gy++; + return gy; +} +function calcScore(n, lvl) { + return [0, 40, 100, 300, 1200][n] * (lvl + 1); +} +function gravityMs(lvl) { + const s = [800, 720, 630, 550, 470, 380, 300, 220, 140, 100, 80, 80, 80, 70, 70, 70, 50, 50, 50, 30]; + return s[Math.min(lvl, s.length - 1)]; +} +const VERT = ` +attribute vec2 a_pos; +attribute vec4 a_col; +uniform vec2 u_res; +varying vec4 v_col; +void main() { + vec2 clip = (a_pos / u_res) * 2.0 - 1.0; + gl_Position = vec4(clip * vec2(1, -1), 0, 1); + v_col = a_col; +}`; +const FRAG = ` +precision mediump float; +varying vec4 v_col; +void main() { gl_FragColor = v_col; }`; +let gl; +let prog; +let aPos, aCol, uRes; +let buf; +function initGL(canvas) { + gl = canvas.getContext("webgl", { alpha: false }); + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0.06, 0.06, 0.1, 1); + function comp(type, src) { + const s = gl.createShader(type); + gl.shaderSource(s, src); + gl.compileShader(s); + return s; + } + const vs = comp(gl.VERTEX_SHADER, VERT); + const fs = comp(gl.FRAGMENT_SHADER, FRAG); + prog = gl.createProgram(); + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + gl.useProgram(prog); + aPos = gl.getAttribLocation(prog, "a_pos"); + aCol = gl.getAttribLocation(prog, "a_col"); + uRes = gl.getUniformLocation(prog, "u_res"); + buf = gl.createBuffer(); +} +function darker(c) { return [c[0] * 0.55, c[1] * 0.55, c[2] * 0.55, c[3]]; } +function lighter(c) { return [Math.min(1, c[0] * 1.35), Math.min(1, c[1] * 1.35), Math.min(1, c[2] * 1.35), c[3]]; } +function pushQuad(v, cl, x, y, w, h, c) { + v.push(x, y, x + w, y, x, y + h, x + w, y, x + w, y + h, x, y + h); + for (let i = 0; i < 6; i++) + cl.push(...c); +} +function pushBlock(v, cl, x, y, s, c) { + const b = 2; + pushQuad(v, cl, x, y, s, s, darker(c)); + pushQuad(v, cl, x + b, y + b, s - 2 * b, s - 2 * b, c); + pushQuad(v, cl, x + b, y + b, s - 2 * b, b, lighter(c)); + pushQuad(v, cl, x + b, y + b, b, s - 2 * b, lighter(c)); +} +let board; +let cur; +let nxt; +let held; +let canHold; +let score, level, linesCleared; +let gameOver, paused; +let lastTick; +let softDrop; +function resetGame() { + board = createGrid(); + cur = makePiece(nextIdx()); + nxt = makePiece(nextIdx()); + held = null; + canHold = true; + score = 0; + level = 0; + linesCleared = 0; + gameOver = false; + paused = false; + lastTick = performance.now(); + softDrop = false; + for (const k of Object.keys(keyTimers)) { + clearTimeout(keyTimers[k]); + clearInterval(keyTimers[k]); + } + keyTimers = {}; +} +function move(dx, dy) { + if (!cur) + return false; + const p = { x: cur.pos.x + dx, y: cur.pos.y + dy }; + if (!collides(board, cur.shape, p)) { + cur.pos = p; + return true; + } + return false; +} +function tryRotate() { + if (!cur) + return; + const rot = rotateCW(cur.shape); + for (const k of KICK_OFFSETS) { + const p = { x: cur.pos.x + k.x, y: cur.pos.y + k.y }; + if (!collides(board, rot, p)) { + cur.shape = rot; + cur.pos = p; + return; + } + } +} +function hardDrop() { + if (!cur) + return; + const gy = ghostY(board, cur.shape, cur.pos); + score += (gy - cur.pos.y) * 2; + cur.pos.y = gy; + lockAndAdvance(); +} +function lockAndAdvance() { + if (!cur) + return; + board = lock(board, cur.shape, cur.pos, cur.color); + const { grid: nb, cleared } = clearLines(board); + board = nb; + if (cleared) { + linesCleared += cleared; + score += calcScore(cleared, level); + level = Math.floor(linesCleared / 10); + } + cur = nxt; + nxt = makePiece(nextIdx()); + canHold = true; + if (cur && collides(board, cur.shape, cur.pos)) + gameOver = true; +} +function doHold() { + if (!cur || !canHold) + return; + canHold = false; + const cc = cur.color, cs = cur.shape.map(r => [...r]); + if (held) { + const hc = held.color, hs = held.shape; + cur = makePieceFrom(hs, hc); + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + } + else { + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + cur = nxt; + nxt = makePiece(nextIdx()); + } +} +let keyTimers = {}; +const DAS = 170, ARR = 50; +function startRepeat(code, fn) { + stopRepeat(code); + keyTimers[code + "_d"] = window.setTimeout(() => { keyTimers[code] = window.setInterval(fn, ARR); }, DAS); +} +function stopRepeat(code) { + clearTimeout(keyTimers[code + "_d"]); + clearInterval(keyTimers[code]); + delete keyTimers[code + "_d"]; + delete keyTimers[code]; +} +function onKeyDown(e) { + if (e.repeat) + return; + switch (e.code) { + case "ArrowLeft": + e.preventDefault(); + move(-1, 0); + startRepeat("ArrowLeft", () => move(-1, 0)); + break; + case "ArrowRight": + e.preventDefault(); + move(1, 0); + startRepeat("ArrowRight", () => move(1, 0)); + break; + case "ArrowDown": + e.preventDefault(); + softDrop = true; + break; + case "ArrowUp": + case "KeyX": + e.preventDefault(); + tryRotate(); + break; + case "KeyZ": + e.preventDefault(); + tryRotate(); + tryRotate(); + tryRotate(); + break; + case "Space": + e.preventDefault(); + hardDrop(); + break; + case "KeyC": + case "ShiftLeft": + case "ShiftRight": + e.preventDefault(); + doHold(); + break; + case "KeyP": + case "Escape": + paused = !paused; + break; + case "KeyR": + resetGame(); + break; + } +} +function onKeyUp(e) { + if (e.code === "ArrowLeft" || e.code === "ArrowRight") + stopRepeat(e.code); + if (e.code === "ArrowDown") + softDrop = false; +} +let canvas2d, ctx2d; +function drawFrame() { + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(uRes, CANVAS_W, CANVAS_H); + const v = [], cl = []; + pushQuad(v, cl, BOARD_PAD, BOARD_PAD, BOARD_W, BOARD_H, [0.05, 0.05, 0.08, 1]); + const gc = [0.12, 0.12, 0.18, 1]; + for (let c = 0; c <= COLS; c++) + pushQuad(v, cl, BOARD_PAD + c * CELL, BOARD_PAD, 1, BOARD_H, gc); + for (let r = 0; r <= ROWS; r++) + pushQuad(v, cl, BOARD_PAD, BOARD_PAD + r * CELL, BOARD_W, 1, gc); + for (let r = 0; r < ROWS; r++) + for (let c = 0; c < COLS; c++) + if (board[r][c] !== Color.Empty) + pushBlock(v, cl, BOARD_PAD + c * CELL, BOARD_PAD + r * CELL, CELL, PALETTE[board[r][c]]); + if (cur) { + const gy = ghostY(board, cur.shape, cur.pos); + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) + continue; + const py = gy + r; + if (py < 0) + continue; + pushQuad(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL + 1, BOARD_PAD + py * CELL + 1, CELL - 2, CELL - 2, [0.3, 0.3, 0.4, 0.35]); + } + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) + continue; + const py = cur.pos.y + r; + if (py < 0) + continue; + pushBlock(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL, BOARD_PAD + py * CELL, CELL, PALETTE[cur.color]); + } + } + const sx = BOARD_PAD + BOARD_W + BOARD_PAD; + const panelH = 130; + pushQuad(v, cl, sx, BOARD_PAD, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (nxt) { + const sz = nxt.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = BOARD_PAD + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (nxt.shape[r][c]) + pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[nxt.color]); + } + const holdY = BOARD_PAD + panelH + 15; + pushQuad(v, cl, sx, holdY, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (held) { + const sz = held.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = holdY + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (held.shape[r][c]) + pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[held.color]); + } + const stride = 6; + const data = new Float32Array(v.length + cl.length); + for (let i = 0, vi = 0, ci = 0; i < data.length; i += stride) { + data[i] = v[vi++]; + data[i + 1] = v[vi++]; + data[i + 2] = cl[ci++]; + data[i + 3] = cl[ci++]; + data[i + 4] = cl[ci++]; + data[i + 5] = cl[ci++]; + } + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); + const bs = stride * 4; + gl.enableVertexAttribArray(aPos); + gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, bs, 0); + gl.enableVertexAttribArray(aCol); + gl.vertexAttribPointer(aCol, 4, gl.FLOAT, false, bs, 8); + gl.drawArrays(gl.TRIANGLES, 0, data.length / stride); + const ctx = ctx2d; + ctx.clearRect(0, 0, CANVAS_W, CANVAS_H); + ctx.font = "bold 14px 'Segoe UI', Arial, sans-serif"; + ctx.fillStyle = "#aaa"; + ctx.textAlign = "left"; + ctx.fillText("NEXT", sx + 8, BOARD_PAD + 18); + ctx.fillText("HOLD", sx + 8, holdY + 18); + const iy = holdY + panelH + 30; + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("SCORE", sx + 8, iy); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(score.toLocaleString(), sx + 8, iy + 24); + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LEVEL", sx + 8, iy + 54); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(level + 1), sx + 8, iy + 78); + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LINES", sx + 8, iy + 108); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(linesCleared), sx + 8, iy + 132); + ctx.fillStyle = "#666"; + ctx.font = "12px 'Segoe UI', Arial, sans-serif"; + const help = ["← → Move", "↑ / X Rotate CW", "Z Rotate CCW", "↓ Soft Drop", "Space Hard Drop", "C Hold", "P Pause", "R Restart"]; + help.forEach((t, i) => ctx.fillText(t, sx + 8, iy + 170 + i * 18)); + if (gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 40, BOARD_W, 80); + ctx.textAlign = "center"; + ctx.fillStyle = "#ff4444"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("GAME OVER", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.fillStyle = "#ccc"; + ctx.font = "16px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("Press R to restart", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 28); + ctx.textAlign = "left"; + } + if (paused && !gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 30, BOARD_W, 60); + ctx.textAlign = "center"; + ctx.fillStyle = "#ffdd44"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("PAUSED", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.textAlign = "left"; + } +} +function tick(now) { + requestAnimationFrame(tick); + if (gameOver || paused) { + drawFrame(); + return; + } + const interval = softDrop ? Math.min(gravityMs(level), 50) : gravityMs(level); + if (now - lastTick >= interval) { + lastTick = now; + if (softDrop) + score += 1; + if (!move(0, 1)) + lockAndAdvance(); + } + drawFrame(); +} +function start() { + const wrapper = document.getElementById("game-wrapper"); + const canvas = document.createElement("canvas"); + canvas.width = CANVAS_W; + canvas.height = CANVAS_H; + canvas.style.display = "block"; + canvas.style.borderRadius = "4px"; + canvas.tabIndex = 0; + wrapper.appendChild(canvas); + canvas2d = document.createElement("canvas"); + canvas2d.width = CANVAS_W; + canvas2d.height = CANVAS_H; + canvas2d.style.position = "absolute"; + canvas2d.style.left = "0"; + canvas2d.style.top = "0"; + canvas2d.style.pointerEvents = "none"; + wrapper.style.position = "relative"; + wrapper.appendChild(canvas2d); + ctx2d = canvas2d.getContext("2d"); + initGL(canvas); + resetGame(); + canvas.addEventListener("keydown", onKeyDown); + canvas.addEventListener("keyup", onKeyUp); + canvas.focus(); + requestAnimationFrame(tick); +} +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start); +} +else { + start(); +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/index.html b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/index.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Tetris – WebGL</title> + <style> + * { margin: 0; padding: 0; box-sizing: border-box; } + body { + background: #0c0c14; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + font-family: 'Segoe UI', Arial, sans-serif; + overflow: hidden; + } + h1 { + color: #e0e0e0; + font-size: 24px; + margin-bottom: 12px; + letter-spacing: 4px; + text-transform: uppercase; + } + #game-wrapper { + position: relative; + display: inline-block; + } + canvas { outline: none; } + #game-wrapper canvas + canvas { + /* text overlay canvas sits on top */ + } + </style> +</head> +<body> + <h1>Tetris</h1> + <div id="game-wrapper"></div> + <script src="build/tetris.js"></script> +</body> +</html> diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/package-lock.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/package-lock.json @@ -0,0 +1,2519 @@ +{ + "name": "loop-bench-718df8zr", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "loop-bench-718df8zr", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "html-validate": "^10.11.3", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.4.tgz", + "integrity": "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.4", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.4.tgz", + "integrity": "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", + "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", + "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", + "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@html-validate/stylish": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@html-validate/stylish/-/stylish-5.1.0.tgz", + "integrity": "sha512-Tyx/ZbHBpVZjvSleNplNMUhqT4UY1HwAMC97GSmasJXggWuvjNFLBS2scqnEb+ZG1szLq4zgjOioj7cVWV9WuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.0.0" + }, + "engines": { + "node": "^20.11 || >= 22.16" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jscpd/badge-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/badge-reporter/-/badge-reporter-4.0.4.tgz", + "integrity": "sha512-I9b4MmLXPM2vo0SxSUWnNGKcA4PjQlD3GzXvFK60z43cN/EIdLbOq3FVwCL+dg2obUqGXKIzAm7EsDFTg0D+mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "badgen": "^3.2.3", + "colors": "^1.4.0", + "fs-extra": "^11.2.0" + } + }, + "node_modules/@jscpd/core": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/core/-/core-4.0.4.tgz", + "integrity": "sha512-QGMT3iXEX1fI6lgjPH+x8eyJwhwr2KkpSF5uBpjC0Z5Xloj0yFTFLtwJT+RhxP/Ob4WYrtx2jvpKB269oIwgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/@jscpd/finder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/finder/-/finder-4.0.4.tgz", + "integrity": "sha512-qVUWY7Nzuvfd5OIk+n7/5CM98LmFroLqblRXAI2gDABwZrc7qS+WH2SNr0qoUq0f4OqwM+piiwKvwL/VDNn/Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "blamer": "^1.0.6", + "bytes": "^3.1.2", + "cli-table3": "^0.6.5", + "colors": "^1.4.0", + "fast-glob": "^3.3.2", + "fs-extra": "^11.2.0", + "markdown-table": "^2.0.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/html-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/html-reporter/-/html-reporter-4.0.4.tgz", + "integrity": "sha512-YiepyeYkeH74Kx59PJRdUdonznct0wHPFkf6FLQN+mCBoy6leAWCcOfHtcexnp+UsBFDlItG5nRdKrDSxSH+Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "1.4.0", + "fs-extra": "^11.2.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/tokenizer": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/tokenizer/-/tokenizer-4.0.4.tgz", + "integrity": "sha512-xxYYY/qaLah/FlwogEbGIxx9CjDO+G9E6qawcy26WwrflzJb6wsnhjwdneN6Wb0RNCDsqvzY+bzG453jsin4UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "reprism": "^0.0.11", + "spark-md5": "^3.0.2" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/badgen": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/badgen/-/badgen-3.2.3.tgz", + "integrity": "sha512-svDuwkc63E/z0ky3drpUppB83s/nlgDciH9m+STwwQoWyq7yCgew1qEfJ+9axkKdNq7MskByptWUN9j1PGMwFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/blamer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/blamer/-/blamer-1.0.7.tgz", + "integrity": "sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^4.0.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gitignore-to-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitignore-to-glob/-/gitignore-to-glob-0.3.0.tgz", + "integrity": "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.4 <5 || >=6.9" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-validate": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/html-validate/-/html-validate-10.11.3.tgz", + "integrity": "sha512-wKUq9iR6bukMgiHhs/ORThZzEbQoFiiPNN7aZfQ8dlmhttPb2sM2Ji2p+Fy5Xj1aH7QHJ1biT2SUDw7A01P2oA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/html-validate" + } + ], + "license": "MIT", + "dependencies": { + "@html-validate/stylish": "^5.0.0", + "@sidvind/better-ajv-errors": "4.0.1", + "ajv": "^8.0.0", + "glob": "^13.0.0", + "kleur": "^4.1.0", + "minimist": "^1.2.0", + "prompts": "^2.0.0", + "semver": "^7.0.0" + }, + "bin": { + "html-validate": "bin/html-validate.mjs" + }, + "engines": { + "node": "^20.19.0 || >= 22.16.0" + }, + "peerDependencies": { + "jest": "^28.1.3 || ^29.0.3 || ^30.0.0", + "jest-diff": "^28.1.3 || ^29.0.3 || ^30.0.0", + "jest-snapshot": "^28.1.3 || ^29.0.3 || ^30.0.0", + "vitest": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.1" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + }, + "jest-diff": { + "optional": true + }, + "jest-snapshot": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/html-validate/node_modules/@sidvind/better-ajv-errors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sidvind/better-ajv-errors/-/better-ajv-errors-4.0.1.tgz", + "integrity": "sha512-6arF1ssKxItxgitPYXafUoLmsVBA6K7m9+ZGj6hLDoBl7nWpJ33EInwQUdHTle2METeWGxgQiqSex20KZRykew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "kleur": "^4.1.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "ajv": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/html-validate/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/html-validate/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jscpd": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-4.0.8.tgz", + "integrity": "sha512-d2VNT/2Hv4dxT2/59He8Lyda4DYOxPRyRG9zBaOpTZAqJCVf2xLrBlZkT8Va6Lo9u3X2qz8Bpq4HrDi4JsrQhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/badge-reporter": "4.0.4", + "@jscpd/core": "4.0.4", + "@jscpd/finder": "4.0.4", + "@jscpd/html-reporter": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "colors": "^1.4.0", + "commander": "^5.0.0", + "fs-extra": "^11.2.0", + "gitignore-to-glob": "^0.3.0", + "jscpd-sarif-reporter": "4.0.6" + }, + "bin": { + "jscpd": "bin/jscpd" + } + }, + "node_modules/jscpd-sarif-reporter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/jscpd-sarif-reporter/-/jscpd-sarif-reporter-4.0.6.tgz", + "integrity": "sha512-b9Sm3IPZ3+m8Lwa4gZa+4/LhDhlc/ZLEsLXKSOy1DANQ6kx0ueqZT+fUHWEdQ6m0o3+RIVIa7DmvLSojQD05ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "fs-extra": "^11.2.0", + "node-sarif-builder": "^3.4.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz", + "integrity": "sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pug": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", + "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.4", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", + "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/reprism": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/reprism/-/reprism-0.0.11.tgz", + "integrity": "sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/package.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/package.json @@ -0,0 +1,21 @@ +{ + "name": "loop-bench-718df8zr", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "html-validate": "^10.11.3", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-few/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-few/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 30_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-few/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-few/tetris.spec.ts @@ -0,0 +1,96 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + }); + + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Give the page a moment to finish initializing + await page.waitForTimeout(2000); + + expect(errors).toEqual([]); + }); + + test("game board is visible", async ({ page }) => { + // A Tetris game should render either a <canvas> or a grid of DOM elements + const canvas = page.locator("canvas"); + const gridContainer = page.locator( + [ + '[class*="board"]', + '[class*="grid"]', + '[class*="game"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); + + const canvasCount = await canvas.count(); + const gridCount = await gridContainer.count(); + + expect( + canvasCount + gridCount, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + }); + + test("keyboard input does not crash the game", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Press every key a Tetris game should handle + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + for (const key of keys) { + await page.keyboard.press(key); + await page.waitForTimeout(150); + } + + expect(errors).toEqual([]); + }); + + test("game state changes over time", async ({ page }) => { + // If the game is running, the visual output should change as pieces fall + const shot1 = await page.screenshot(); + await page.waitForTimeout(3000); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the page to visually change over 3 seconds (pieces should be falling)" + ).toBe(false); + }); +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-full/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-full/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 60_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-full/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tests-full/tetris.spec.ts @@ -0,0 +1,474 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +// Find the game surface: canvas or a grid-like DOM container. +function gameBoard(page: Page) { + return page.locator( + [ + "canvas", + '[class*="board"]', + '[class*="grid"]', + '[class*="game-area"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); +} + +// Click the board area to make sure it has focus, then try common +// start interactions in case the game waits for user action. +async function ensureGameStarted(page: Page) { + const board = gameBoard(page); + const count = await board.count(); + if (count > 0) { + try { + await board.first().click({ timeout: 2000 }); + } catch { + // click failed, continue anyway + } + } + + // Some games need a key press or button click to start + const startButton = page.locator( + 'button:has-text("start"), button:has-text("Start"), button:has-text("play"), button:has-text("Play"), [class*="start"], [id*="start"]' + ); + if ((await startButton.count()) > 0) { + try { + await startButton.first().click({ timeout: 2000 }); + } catch { + // ignore + } + } + + // Press Enter/Space as a fallback start trigger + await page.keyboard.press("Enter"); + await page.waitForTimeout(300); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + await page.waitForTimeout(1000); + await ensureGameStarted(page); + }); + + // ---- 1. Page loads without errors ---- + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + await page.waitForTimeout(2000); + expect(errors).toEqual([]); + }); + + // ---- 2. Game board is visible ---- + test("game board is visible", async ({ page }) => { + const board = gameBoard(page); + const count = await board.count(); + + expect( + count, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + + // The board element should have meaningful dimensions + const box = await board.first().boundingBox(); + expect(box, "Game board should be visible on screen").not.toBeNull(); + expect(box!.width).toBeGreaterThan(50); + expect(box!.height).toBeGreaterThan(50); + }); + + // ---- 3. Game starts automatically or via interaction ---- + test("game starts", async ({ page }) => { + // After beforeEach, the game should be running. Verify by checking that + // the page is not static: take two screenshots separated by time. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the game to show visual activity after starting" + ).toBe(false); + }); + + // ---- 4. Piece falls automatically (auto-drop) ---- + test("piece falls automatically", async ({ page }) => { + // Take screenshots at intervals without pressing any keys. + // A falling piece should cause visual changes. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot3 = await page.screenshot(); + + const buf1 = Buffer.from(shot1); + const buf2 = Buffer.from(shot2); + const buf3 = Buffer.from(shot3); + + // At least one pair should differ (piece is moving down) + const changed = !buf1.equals(buf2) || !buf2.equals(buf3); + expect(changed, "Expected piece to fall over time without input").toBe( + true + ); + }); + + // ---- 5. Left arrow moves piece left ---- + test("left arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + // The piece should have moved, so the screenshots should differ + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing left arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 6. Right arrow moves piece right ---- + test("right arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing right arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 7. Down arrow moves piece down faster ---- + test("down arrow accelerates piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + for (let i = 0; i < 10; i++) { + await page.keyboard.press("ArrowDown"); + await page.waitForTimeout(50); + } + await page.waitForTimeout(200); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing down arrow repeatedly" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 8. Up arrow (or Z) rotates piece ---- + test("rotation changes the piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing rotate key" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 9. Space bar hard-drops piece ---- + test("space bar hard-drops piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing space (hard drop)" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 10. Pieces lock at the bottom ---- + test("pieces lock at the bottom", async ({ page }) => { + // Hard-drop several pieces and check that the bottom of the board + // accumulates filled cells (the visual should change cumulatively). + const shots: Buffer[] = []; + + shots.push(Buffer.from(await page.screenshot())); + + for (let i = 0; i < 3; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(800); + } + + shots.push(Buffer.from(await page.screenshot())); + + // After 3 hard drops, the board should look different from the start + // because pieces have stacked up at the bottom. + expect( + shots[0].equals(shots[1]), + "Expected pieces to stack up at the bottom after hard drops" + ).toBe(false); + }); + + // ---- 11. New piece spawns after lock ---- + test("new piece spawns after locking", async ({ page }) => { + // Hard-drop to lock a piece, then wait and verify the game is still + // showing activity (a new piece should be falling). + await page.keyboard.press("Space"); + await page.waitForTimeout(1000); + + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + + // If a new piece spawned and is falling, the screen should change + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected a new piece to spawn and fall after the previous one locked" + ).toBe(false); + }); + + // ---- 12. Multiple different pieces appear ---- + test("multiple different pieces appear", async ({ page }) => { + // Play through several pieces and capture screenshots. Different piece + // shapes should produce visually distinct patterns. + const shots: Buffer[] = []; + + for (let i = 0; i < 6; i++) { + // Move each piece to a different column so they don't overlap identically + if (i % 2 === 0) { + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + } else { + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(600); + shots.push(Buffer.from(await page.screenshot())); + } + + // At least some consecutive screenshots should differ (different piece shapes) + let differences = 0; + for (let i = 1; i < shots.length; i++) { + if (!shots[i - 1].equals(shots[i])) differences++; + } + + expect( + differences, + "Expected to see visual differences between consecutive pieces (different shapes)" + ).toBeGreaterThanOrEqual(2); + }); + + // ---- 13. Completed line clears ---- + test("completed line clears", async ({ page }) => { + // Fill a row by dropping many pieces. Observe whether any row disappears. + // We can detect this by tracking the total filled area -- after a line clear, + // the board should have less filled content than just before the clear. + const pageText = async () => + (await page.evaluate(() => document.body.innerText)) || ""; + + // Drop many pieces rapidly to fill rows + for (let i = 0; i < 30; i++) { + // Vary positions to try to complete a row + const moves = (i % 5) - 2; // -2, -1, 0, 1, 2 + for (let m = 0; m < Math.abs(moves); m++) { + await page.keyboard.press( + moves < 0 ? "ArrowLeft" : "ArrowRight" + ); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + // Check if a score or lines counter changed (common indicators of line clears) + const text = await pageText(); + const numbers = (text.match(/\d+/g) || []).map(Number); + const hasNonZero = numbers.some((n) => n > 0); + + // Also check visual: the board should not be completely static + const shot1 = await page.screenshot(); + await page.waitForTimeout(1000); + const shot2 = await page.screenshot(); + + // Either: score/lines increased, or game is still active (meaning lines cleared + // and made room for more pieces instead of game over) + const stillActive = !Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + hasNonZero || stillActive, + "Expected evidence of line clearing (score > 0 or game still active after many drops)" + ).toBe(true); + }); + + // ---- 14. Score increases during play ---- + test("score increases during play", async ({ page }) => { + // Look for a score display on the page + const getNumbers = async () => { + const text = (await page.evaluate(() => document.body.innerText)) || ""; + return (text.match(/\d+/g) || []).map(Number); + }; + + const numbersBefore = await getNumbers(); + + // Play for a while: drop several pieces + for (let i = 0; i < 15; i++) { + const offset = (i % 5) - 2; + for (let m = 0; m < Math.abs(offset); m++) { + await page.keyboard.press(offset < 0 ? "ArrowLeft" : "ArrowRight"); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + const numbersAfter = await getNumbers(); + + // At least one number on the page should have increased + // (score, lines counter, level, etc.) + let anyIncreased = false; + const maxLen = Math.min(numbersBefore.length, numbersAfter.length); + for (let i = 0; i < maxLen; i++) { + if (numbersAfter[i] > numbersBefore[i]) { + anyIncreased = true; + break; + } + } + + // Also accept if new numbers appeared + if (!anyIncreased && numbersAfter.length > numbersBefore.length) { + anyIncreased = true; + } + + // Also accept if the max number increased + if (!anyIncreased) { + const maxBefore = numbersBefore.length > 0 ? Math.max(...numbersBefore) : 0; + const maxAfter = numbersAfter.length > 0 ? Math.max(...numbersAfter) : 0; + if (maxAfter > maxBefore) anyIncreased = true; + } + + expect( + anyIncreased, + "Expected at least one numeric value on the page to increase during play (score, lines, level)" + ).toBe(true); + }); + + // ---- 15. Game over when pieces reach top ---- + test("game over when pieces reach top", async ({ page }) => { + // Stack pieces in the center until the game ends. + // Drop as many pieces as possible straight down. + for (let i = 0; i < 50; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(200); + } + + await page.waitForTimeout(2000); + + // After stacking to overflow, the game should show some game-over indicator: + // - text saying "game over", "you lose", "try again", "restart", "end" + // - or the game stops updating (static screen) + const text = ((await page.evaluate(() => document.body.innerText)) || "").toLowerCase(); + const gameOverText = + text.includes("game over") || + text.includes("gameover") || + text.includes("you lose") || + text.includes("try again") || + text.includes("restart") || + text.includes("play again") || + text.includes("ended"); + + // Check if the game stopped (screen is static) + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + const isStatic = Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + gameOverText || isStatic, + "Expected game-over text or the game to stop after stacking pieces to the top" + ).toBe(true); + }); + + // ---- 16. Game runs for 30 seconds without crashing ---- + test("game runs for 30 seconds without crashing", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Simulate varied gameplay for 30 seconds + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + const start = Date.now(); + + while (Date.now() - start < 30_000) { + const key = keys[Math.floor(Math.random() * keys.length)]; + await page.keyboard.press(key); + await page.waitForTimeout(150 + Math.random() * 200); + } + + // The page should still be alive (not crashed, not blank) + const text = await page.evaluate(() => document.body.innerText); + expect(text, "Page body should not be empty after 30s of play").toBeTruthy(); + expect( + errors.length, + `Expected no console errors during 30s of play, got: ${errors.join("; ")}` + ).toBe(0); + }); +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/build/tetris.js b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/build/tetris.js @@ -0,0 +1,516 @@ +"use strict"; +var Color; +(function (Color) { + Color[Color["Empty"] = 0] = "Empty"; + Color[Color["Cyan"] = 1] = "Cyan"; + Color[Color["Yellow"] = 2] = "Yellow"; + Color[Color["Purple"] = 3] = "Purple"; + Color[Color["Green"] = 4] = "Green"; + Color[Color["Red"] = 5] = "Red"; + Color[Color["Blue"] = 6] = "Blue"; + Color[Color["Orange"] = 7] = "Orange"; +})(Color || (Color = {})); +const COLS = 10; +const ROWS = 20; +const CELL = 30; +const BOARD_PAD = 20; +const BOARD_W = COLS * CELL; +const BOARD_H = ROWS * CELL; +const SIDEBAR_W = 200; +const CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD; +const CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD; +const PALETTE = { + [Color.Empty]: [0.08, 0.08, 0.12, 1], + [Color.Cyan]: [0.0, 0.9, 0.9, 1], + [Color.Yellow]: [0.95, 0.9, 0.1, 1], + [Color.Purple]: [0.7, 0.2, 0.9, 1], + [Color.Green]: [0.1, 0.9, 0.3, 1], + [Color.Red]: [0.9, 0.2, 0.2, 1], + [Color.Blue]: [0.2, 0.3, 0.9, 1], + [Color.Orange]: [0.95, 0.55, 0.1, 1], +}; +const SHAPES = [ + { shape: [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], color: Color.Cyan }, + { shape: [[1, 1], [1, 1]], color: Color.Yellow }, + { shape: [[0, 1, 0], [1, 1, 1], [0, 0, 0]], color: Color.Purple }, + { shape: [[0, 1, 1], [1, 1, 0], [0, 0, 0]], color: Color.Green }, + { shape: [[1, 1, 0], [0, 1, 1], [0, 0, 0]], color: Color.Red }, + { shape: [[1, 0, 0], [1, 1, 1], [0, 0, 0]], color: Color.Blue }, + { shape: [[0, 0, 1], [1, 1, 1], [0, 0, 0]], color: Color.Orange }, +]; +const KICK_OFFSETS = [ + { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 1, y: 0 }, + { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 }, + { x: -2, y: 0 }, { x: 2, y: 0 }, +]; +let bag = []; +function refillBag() { + bag = [0, 1, 2, 3, 4, 5, 6]; + for (let i = bag.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [bag[i], bag[j]] = [bag[j], bag[i]]; + } +} +function nextIdx() { + if (bag.length === 0) + refillBag(); + return bag.pop(); +} +function makePiece(idx) { + const def = SHAPES[idx]; + const shape = def.shape.map(r => [...r]); + return { shape, color: def.color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} +function makePieceFrom(shape, color) { + return { shape: shape.map(r => [...r]), color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} +function rotateCW(shape) { + const n = shape.length; + const out = []; + for (let r = 0; r < n; r++) { + out.push([]); + for (let c = 0; c < n; c++) + out[r].push(shape[n - 1 - c][r]); + } + return out; +} +function createGrid() { + const g = []; + for (let r = 0; r < ROWS; r++) + g.push(new Array(COLS).fill(Color.Empty)); + return g; +} +function collides(grid, shape, pos) { + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const nx = pos.x + c, ny = pos.y + r; + if (nx < 0 || nx >= COLS || ny >= ROWS) + return true; + if (ny >= 0 && grid[ny][nx] !== Color.Empty) + return true; + } + return false; +} +function lock(grid, shape, pos, color) { + const g = grid.map(r => [...r]); + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) + continue; + const ny = pos.y + r, nx = pos.x + c; + if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) + g[ny][nx] = color; + } + return g; +} +function clearLines(grid) { + const remaining = grid.filter(row => row.some(c => c === Color.Empty)); + const cleared = ROWS - remaining.length; + while (remaining.length < ROWS) + remaining.unshift(new Array(COLS).fill(Color.Empty)); + return { grid: remaining, cleared }; +} +function ghostY(grid, shape, pos) { + let gy = pos.y; + while (!collides(grid, shape, { x: pos.x, y: gy + 1 })) + gy++; + return gy; +} +function calcScore(n, lvl) { + return [0, 40, 100, 300, 1200][n] * (lvl + 1); +} +function gravityMs(lvl) { + const s = [800, 720, 630, 550, 470, 380, 300, 220, 140, 100, 80, 80, 80, 70, 70, 70, 50, 50, 50, 30]; + return s[Math.min(lvl, s.length - 1)]; +} +const VERT = ` +attribute vec2 a_pos; +attribute vec4 a_col; +uniform vec2 u_res; +varying vec4 v_col; +void main() { + vec2 clip = (a_pos / u_res) * 2.0 - 1.0; + gl_Position = vec4(clip * vec2(1, -1), 0, 1); + v_col = a_col; +}`; +const FRAG = ` +precision mediump float; +varying vec4 v_col; +void main() { gl_FragColor = v_col; }`; +let gl; +let prog; +let aPos, aCol, uRes; +let buf; +function initGL(canvas) { + gl = canvas.getContext("webgl", { alpha: false }); + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0.06, 0.06, 0.1, 1); + function comp(type, src) { + const s = gl.createShader(type); + gl.shaderSource(s, src); + gl.compileShader(s); + return s; + } + const vs = comp(gl.VERTEX_SHADER, VERT); + const fs = comp(gl.FRAGMENT_SHADER, FRAG); + prog = gl.createProgram(); + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + gl.useProgram(prog); + aPos = gl.getAttribLocation(prog, "a_pos"); + aCol = gl.getAttribLocation(prog, "a_col"); + uRes = gl.getUniformLocation(prog, "u_res"); + buf = gl.createBuffer(); +} +function darker(c) { return [c[0] * 0.55, c[1] * 0.55, c[2] * 0.55, c[3]]; } +function lighter(c) { return [Math.min(1, c[0] * 1.35), Math.min(1, c[1] * 1.35), Math.min(1, c[2] * 1.35), c[3]]; } +function pushQuad(v, cl, x, y, w, h, c) { + v.push(x, y, x + w, y, x, y + h, x + w, y, x + w, y + h, x, y + h); + for (let i = 0; i < 6; i++) + cl.push(...c); +} +function pushBlock(v, cl, x, y, s, c) { + const b = 2; + pushQuad(v, cl, x, y, s, s, darker(c)); + pushQuad(v, cl, x + b, y + b, s - 2 * b, s - 2 * b, c); + pushQuad(v, cl, x + b, y + b, s - 2 * b, b, lighter(c)); + pushQuad(v, cl, x + b, y + b, b, s - 2 * b, lighter(c)); +} +let board; +let cur; +let nxt; +let held; +let canHold; +let score, level, linesCleared; +let gameOver, paused; +let lastTick; +let softDrop; +function resetGame() { + board = createGrid(); + cur = makePiece(nextIdx()); + nxt = makePiece(nextIdx()); + held = null; + canHold = true; + score = 0; + level = 0; + linesCleared = 0; + gameOver = false; + paused = false; + lastTick = performance.now(); + softDrop = false; + for (const k of Object.keys(keyTimers)) { + clearTimeout(keyTimers[k]); + clearInterval(keyTimers[k]); + } + keyTimers = {}; +} +function move(dx, dy) { + if (!cur) + return false; + const p = { x: cur.pos.x + dx, y: cur.pos.y + dy }; + if (!collides(board, cur.shape, p)) { + cur.pos = p; + return true; + } + return false; +} +function tryRotate() { + if (!cur) + return; + const rot = rotateCW(cur.shape); + for (const k of KICK_OFFSETS) { + const p = { x: cur.pos.x + k.x, y: cur.pos.y + k.y }; + if (!collides(board, rot, p)) { + cur.shape = rot; + cur.pos = p; + return; + } + } +} +function hardDrop() { + if (!cur) + return; + const gy = ghostY(board, cur.shape, cur.pos); + score += (gy - cur.pos.y) * 2; + cur.pos.y = gy; + lockAndAdvance(); +} +function lockAndAdvance() { + if (!cur) + return; + board = lock(board, cur.shape, cur.pos, cur.color); + const { grid: nb, cleared } = clearLines(board); + board = nb; + if (cleared) { + linesCleared += cleared; + score += calcScore(cleared, level); + level = Math.floor(linesCleared / 10); + } + cur = nxt; + nxt = makePiece(nextIdx()); + canHold = true; + if (cur && collides(board, cur.shape, cur.pos)) + gameOver = true; +} +function doHold() { + if (!cur || !canHold) + return; + canHold = false; + const cc = cur.color, cs = cur.shape.map(r => [...r]); + if (held) { + const hc = held.color, hs = held.shape; + cur = makePieceFrom(hs, hc); + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + } + else { + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + cur = nxt; + nxt = makePiece(nextIdx()); + } +} +let keyTimers = {}; +const DAS = 170, ARR = 50; +function startRepeat(code, fn) { + stopRepeat(code); + keyTimers[code + "_d"] = window.setTimeout(() => { keyTimers[code] = window.setInterval(fn, ARR); }, DAS); +} +function stopRepeat(code) { + clearTimeout(keyTimers[code + "_d"]); + clearInterval(keyTimers[code]); + delete keyTimers[code + "_d"]; + delete keyTimers[code]; +} +function onKeyDown(e) { + if (e.repeat) + return; + switch (e.code) { + case "ArrowLeft": + e.preventDefault(); + move(-1, 0); + startRepeat("ArrowLeft", () => move(-1, 0)); + break; + case "ArrowRight": + e.preventDefault(); + move(1, 0); + startRepeat("ArrowRight", () => move(1, 0)); + break; + case "ArrowDown": + e.preventDefault(); + softDrop = true; + break; + case "ArrowUp": + case "KeyX": + e.preventDefault(); + tryRotate(); + break; + case "KeyZ": + e.preventDefault(); + tryRotate(); + tryRotate(); + tryRotate(); + break; + case "Space": + e.preventDefault(); + hardDrop(); + break; + case "KeyC": + case "ShiftLeft": + case "ShiftRight": + e.preventDefault(); + doHold(); + break; + case "KeyP": + case "Escape": + paused = !paused; + break; + case "KeyR": + resetGame(); + break; + } +} +function onKeyUp(e) { + if (e.code === "ArrowLeft" || e.code === "ArrowRight") + stopRepeat(e.code); + if (e.code === "ArrowDown") + softDrop = false; +} +let canvas2d, ctx2d; +function drawFrame() { + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(uRes, CANVAS_W, CANVAS_H); + const v = [], cl = []; + pushQuad(v, cl, BOARD_PAD, BOARD_PAD, BOARD_W, BOARD_H, [0.05, 0.05, 0.08, 1]); + const gc = [0.12, 0.12, 0.18, 1]; + for (let c = 0; c <= COLS; c++) + pushQuad(v, cl, BOARD_PAD + c * CELL, BOARD_PAD, 1, BOARD_H, gc); + for (let r = 0; r <= ROWS; r++) + pushQuad(v, cl, BOARD_PAD, BOARD_PAD + r * CELL, BOARD_W, 1, gc); + for (let r = 0; r < ROWS; r++) + for (let c = 0; c < COLS; c++) + if (board[r][c] !== Color.Empty) + pushBlock(v, cl, BOARD_PAD + c * CELL, BOARD_PAD + r * CELL, CELL, PALETTE[board[r][c]]); + if (cur) { + const gy = ghostY(board, cur.shape, cur.pos); + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) + continue; + const py = gy + r; + if (py < 0) + continue; + pushQuad(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL + 1, BOARD_PAD + py * CELL + 1, CELL - 2, CELL - 2, [0.3, 0.3, 0.4, 0.35]); + } + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) + continue; + const py = cur.pos.y + r; + if (py < 0) + continue; + pushBlock(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL, BOARD_PAD + py * CELL, CELL, PALETTE[cur.color]); + } + } + const sx = BOARD_PAD + BOARD_W + BOARD_PAD; + const panelH = 130; + pushQuad(v, cl, sx, BOARD_PAD, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (nxt) { + const sz = nxt.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = BOARD_PAD + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (nxt.shape[r][c]) + pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[nxt.color]); + } + const holdY = BOARD_PAD + panelH + 15; + pushQuad(v, cl, sx, holdY, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (held) { + const sz = held.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = holdY + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (held.shape[r][c]) + pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[held.color]); + } + const stride = 6; + const data = new Float32Array(v.length + cl.length); + for (let i = 0, vi = 0, ci = 0; i < data.length; i += stride) { + data[i] = v[vi++]; + data[i + 1] = v[vi++]; + data[i + 2] = cl[ci++]; + data[i + 3] = cl[ci++]; + data[i + 4] = cl[ci++]; + data[i + 5] = cl[ci++]; + } + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); + const bs = stride * 4; + gl.enableVertexAttribArray(aPos); + gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, bs, 0); + gl.enableVertexAttribArray(aCol); + gl.vertexAttribPointer(aCol, 4, gl.FLOAT, false, bs, 8); + gl.drawArrays(gl.TRIANGLES, 0, data.length / stride); + const ctx = ctx2d; + ctx.clearRect(0, 0, CANVAS_W, CANVAS_H); + ctx.font = "bold 14px 'Segoe UI', Arial, sans-serif"; + ctx.fillStyle = "#aaa"; + ctx.textAlign = "left"; + ctx.fillText("NEXT", sx + 8, BOARD_PAD + 18); + ctx.fillText("HOLD", sx + 8, holdY + 18); + const iy = holdY + panelH + 30; + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("SCORE", sx + 8, iy); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(score.toLocaleString(), sx + 8, iy + 24); + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LEVEL", sx + 8, iy + 54); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(level + 1), sx + 8, iy + 78); + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LINES", sx + 8, iy + 108); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(linesCleared), sx + 8, iy + 132); + ctx.fillStyle = "#666"; + ctx.font = "12px 'Segoe UI', Arial, sans-serif"; + const help = ["← → Move", "↑ / X Rotate CW", "Z Rotate CCW", "↓ Soft Drop", "Space Hard Drop", "C Hold", "P Pause", "R Restart"]; + help.forEach((t, i) => ctx.fillText(t, sx + 8, iy + 170 + i * 18)); + if (gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 40, BOARD_W, 80); + ctx.textAlign = "center"; + ctx.fillStyle = "#ff4444"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("GAME OVER", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.fillStyle = "#ccc"; + ctx.font = "16px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("Press R to restart", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 28); + ctx.textAlign = "left"; + } + if (paused && !gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 30, BOARD_W, 60); + ctx.textAlign = "center"; + ctx.fillStyle = "#ffdd44"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("PAUSED", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.textAlign = "left"; + } +} +function tick(now) { + requestAnimationFrame(tick); + if (gameOver || paused) { + drawFrame(); + return; + } + const interval = softDrop ? Math.min(gravityMs(level), 50) : gravityMs(level); + if (now - lastTick >= interval) { + lastTick = now; + if (softDrop) + score += 1; + if (!move(0, 1)) + lockAndAdvance(); + } + drawFrame(); +} +function start() { + const wrapper = document.getElementById("game-wrapper"); + const canvas = document.createElement("canvas"); + canvas.width = CANVAS_W; + canvas.height = CANVAS_H; + canvas.style.display = "block"; + canvas.style.borderRadius = "4px"; + canvas.tabIndex = 0; + wrapper.appendChild(canvas); + canvas2d = document.createElement("canvas"); + canvas2d.width = CANVAS_W; + canvas2d.height = CANVAS_H; + canvas2d.style.position = "absolute"; + canvas2d.style.left = "0"; + canvas2d.style.top = "0"; + canvas2d.style.pointerEvents = "none"; + wrapper.style.position = "relative"; + wrapper.appendChild(canvas2d); + ctx2d = canvas2d.getContext("2d"); + initGL(canvas); + resetGame(); + canvas.addEventListener("keydown", onKeyDown); + canvas.addEventListener("keyup", onKeyUp); + canvas.focus(); + requestAnimationFrame(tick); +} +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start); +} +else { + start(); +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/index.html b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/index.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>Tetris – WebGL</title> + <style> + * { margin: 0; padding: 0; box-sizing: border-box; } + body { + background: #0c0c14; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + font-family: 'Segoe UI', Arial, sans-serif; + overflow: hidden; + } + h1 { + color: #e0e0e0; + font-size: 24px; + margin-bottom: 12px; + letter-spacing: 4px; + text-transform: uppercase; + } + #game-wrapper { + position: relative; + display: inline-block; + } + canvas { outline: none; } + #game-wrapper canvas + canvas { + /* text overlay canvas sits on top */ + } + </style> +</head> +<body> + <h1>Tetris</h1> + <div id="game-wrapper"></div> + <script src="build/tetris.js"></script> +</body> +</html> diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/board.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/board.ts @@ -0,0 +1,73 @@ +// ── Board Logic ── + +import { Grid, Color, Shape, Position } from "./types"; + +export const COLS = 10; +export const ROWS = 20; + +export function createGrid(): Grid { + const grid: Grid = []; + for (let r = 0; r < ROWS; r++) { + grid.push(new Array<Color>(COLS).fill(Color.Empty)); + } + return grid; +} + +export function collides(grid: Grid, shape: Shape, pos: Position): boolean { + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (shape[r][c] === 0) continue; + const nx = pos.x + c; + const ny = pos.y + r; + if (nx < 0 || nx >= COLS || ny >= ROWS) return true; + if (ny >= 0 && grid[ny][nx] !== Color.Empty) return true; + } + } + return false; +} + +export function lockPiece(grid: Grid, shape: Shape, pos: Position, color: Color): Grid { + const g = grid.map(row => [...row]); + for (let r = 0; r < shape.length; r++) { + for (let c = 0; c < shape[r].length; c++) { + if (shape[r][c] === 0) continue; + const ny = pos.y + r; + const nx = pos.x + c; + if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) { + g[ny][nx] = color; + } + } + } + return g; +} + +export function clearLines(grid: Grid): { grid: Grid; cleared: number } { + const g = grid.map(row => [...row]); + const remaining = g.filter(row => row.some(c => c === Color.Empty)); + const cleared = ROWS - remaining.length; + while (remaining.length < ROWS) { + remaining.unshift(new Array<Color>(COLS).fill(Color.Empty)); + } + return { grid: remaining, cleared }; +} + +export function ghostY(grid: Grid, shape: Shape, pos: Position): number { + let gy = pos.y; + while (!collides(grid, shape, { x: pos.x, y: gy + 1 })) { + gy++; + } + return gy; +} + +// Scoring: NES-style +export function calcScore(linesCleared: number, level: number): number { + const base = [0, 40, 100, 300, 1200]; + return (base[linesCleared] || 0) * (level + 1); +} + +// Speed in ms per gravity tick +export function gravityMs(level: number): number { + // Roughly NES curve + const speeds = [800, 720, 630, 550, 470, 380, 300, 220, 140, 100, 80, 80, 80, 70, 70, 70, 50, 50, 50, 30]; + return speeds[Math.min(level, speeds.length - 1)]; +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/game.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/game.ts @@ -0,0 +1,286 @@ +// ── Main Game Entry Point ── + +import { Color, Piece, Shape, GameState } from "./types"; +import { nextPieceIndex, makePiece, rotateCW, KICK_OFFSETS } from "./pieces"; +import { createGrid, collides, lockPiece, clearLines, ghostY, calcScore, gravityMs, COLS, ROWS } from "./board"; +import { initGL, initTextOverlay, render, CANVAS_W, CANVAS_H } from "./renderer"; + +// ── Game State ── +let state: GameState; +let glRes: ReturnType<typeof initGL>; +let lastTick: number; +let animId: number; +let softDropping: boolean; + +function newState(): GameState { + const cur = makePiece(nextPieceIndex()); + const nxt = makePiece(nextPieceIndex()); + return { + board: createGrid(), + current: cur, + next: nxt, + held: null, + canHold: true, + score: 0, + level: 0, + lines: 0, + gameOver: false, + paused: false, + }; +} + +// ── Piece Spawning & Movement ── + +function spawnNext(): void { + state.current = state.next; + state.next = makePiece(nextPieceIndex()); + state.canHold = true; + if (state.current && collides(state.board, state.current.shape, state.current.pos)) { + state.gameOver = true; + } +} + +function move(dx: number, dy: number): boolean { + if (!state.current) return false; + const newPos = { x: state.current.pos.x + dx, y: state.current.pos.y + dy }; + if (!collides(state.board, state.current.shape, newPos)) { + state.current.pos = newPos; + return true; + } + return false; +} + +function tryRotate(): void { + if (!state.current) return; + const rotated = rotateCW(state.current.shape); + for (const kick of KICK_OFFSETS) { + const newPos = { x: state.current.pos.x + kick.x, y: state.current.pos.y + kick.y }; + if (!collides(state.board, rotated, newPos)) { + state.current.shape = rotated; + state.current.pos = newPos; + return; + } + } +} + +function hardDrop(): void { + if (!state.current) return; + const gy = ghostY(state.board, state.current.shape, state.current.pos); + state.score += (gy - state.current.pos.y) * 2; + state.current.pos.y = gy; + lockAndAdvance(); +} + +function lockAndAdvance(): void { + if (!state.current) return; + state.board = lockPiece(state.board, state.current.shape, state.current.pos, state.current.color); + const { grid: newBoard, cleared } = clearLines(state.board); + state.board = newBoard; + if (cleared > 0) { + state.lines += cleared; + state.score += calcScore(cleared, state.level); + state.level = Math.floor(state.lines / 10); + } + spawnNext(); +} + +function holdPiece(): void { + if (!state.current || !state.canHold) return; + state.canHold = false; + const curColor = state.current.color; + const curShape = state.current.shape.map(r => [...r]); + if (state.held) { + // Swap + const heldColor = state.held.color; + const heldShape = state.held.shape; + state.current = makePieceFrom(heldShape, heldColor); + state.held = { shape: curShape, color: curColor, pos: { x: 0, y: 0 } }; + } else { + state.held = { shape: curShape, color: curColor, pos: { x: 0, y: 0 } }; + spawnNext(); + } +} + +function makePieceFrom(shape: Shape, color: Color): Piece { + const size = shape.length; + return { + shape: shape.map(r => [...r]), + color, + pos: { + x: Math.floor((COLS - size) / 2), + y: 0, + }, + }; +} + +// ── Gravity Tick ── + +function tick(now: number): void { + animId = requestAnimationFrame(tick); + + if (state.gameOver || state.paused) { + draw(); + return; + } + + const elapsed = now - lastTick; + const interval = softDropping ? Math.min(gravityMs(state.level), 50) : gravityMs(state.level); + + if (elapsed >= interval) { + lastTick = now; + if (softDropping) state.score += 1; + if (!move(0, 1)) { + lockAndAdvance(); + } + } + + draw(); +} + +function draw(): void { + const cur = state.current; + const ghostPos = cur ? { x: cur.pos.x, y: ghostY(state.board, cur.shape, cur.pos) } : null; + + render( + glRes, + state.board, + cur ? cur.shape : null, + cur ? cur.color : null, + cur ? cur.pos : null, + cur ? cur.shape : null, + ghostPos, + state.next ? state.next.shape : null, + state.next ? state.next.color : null, + state.held ? state.held.shape : null, + state.held ? state.held.color : null, + state.score, + state.level, + state.lines, + state.gameOver, + state.paused, + ); +} + +// ── Input ── + +let keyRepeatTimers: Record<string, number> = {}; +const DAS = 170; // Delayed Auto Shift (ms) +const ARR = 50; // Auto Repeat Rate (ms) + +function handleKey(e: KeyboardEvent, down: boolean): void { + if (down) { + if (e.repeat) return; // we handle repeat ourselves for movement + switch (e.code) { + case "ArrowLeft": + e.preventDefault(); + move(-1, 0); + startRepeat("ArrowLeft", () => move(-1, 0)); + break; + case "ArrowRight": + e.preventDefault(); + move(1, 0); + startRepeat("ArrowRight", () => move(1, 0)); + break; + case "ArrowDown": + e.preventDefault(); + softDropping = true; + break; + case "ArrowUp": + case "KeyX": + e.preventDefault(); + tryRotate(); + break; + case "KeyZ": + e.preventDefault(); + // Counter-clockwise = 3 clockwise rotations + tryRotate(); tryRotate(); tryRotate(); + break; + case "Space": + e.preventDefault(); + hardDrop(); + break; + case "KeyC": + case "ShiftLeft": + case "ShiftRight": + e.preventDefault(); + holdPiece(); + break; + case "KeyP": + case "Escape": + state.paused = !state.paused; + break; + case "KeyR": + resetGame(); + break; + } + } else { + switch (e.code) { + case "ArrowLeft": + case "ArrowRight": + stopRepeat(e.code); + break; + case "ArrowDown": + softDropping = false; + break; + } + } +} + +function startRepeat(code: string, fn: () => void): void { + stopRepeat(code); + const id = window.setTimeout(() => { + keyRepeatTimers[code] = window.setInterval(fn, ARR); + }, DAS); + keyRepeatTimers[code + "_das"] = id; +} + +function stopRepeat(code: string): void { + clearTimeout(keyRepeatTimers[code + "_das"]); + clearInterval(keyRepeatTimers[code]); + delete keyRepeatTimers[code + "_das"]; + delete keyRepeatTimers[code]; +} + +// ── Init ── + +function resetGame(): void { + state = newState(); + lastTick = performance.now(); + softDropping = false; + // Clear repeat timers + for (const k of Object.keys(keyRepeatTimers)) { + clearTimeout(keyRepeatTimers[k]); + clearInterval(keyRepeatTimers[k]); + } + keyRepeatTimers = {}; +} + +function start(): void { + const wrapper = document.getElementById("game-wrapper")!; + const canvas = document.createElement("canvas"); + canvas.id = "tetris-canvas"; + canvas.width = CANVAS_W; + canvas.height = CANVAS_H; + canvas.style.display = "block"; + canvas.style.borderRadius = "4px"; + canvas.tabIndex = 0; + wrapper.appendChild(canvas); + + glRes = initGL(canvas); + initTextOverlay(wrapper); + + resetGame(); + + canvas.addEventListener("keydown", e => handleKey(e, true)); + canvas.addEventListener("keyup", e => handleKey(e, false)); + canvas.focus(); + + animId = requestAnimationFrame(tick); +} + +// Boot +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start); +} else { + start(); +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/pieces.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/pieces.ts @@ -0,0 +1,71 @@ +// ── Tetromino Definitions & Bag Randomizer ── + +import { Shape, Color, Piece, Position } from "./types"; + +// Standard Tetris shapes (in their spawn rotation, row-major) +// 0 = empty, non-zero = filled +const SHAPES: { shape: Shape; color: Color }[] = [ + { shape: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], color: Color.Cyan }, // I + { shape: [[1,1],[1,1]], color: Color.Yellow }, // O + { shape: [[0,1,0],[1,1,1],[0,0,0]], color: Color.Purple }, // T + { shape: [[0,1,1],[1,1,0],[0,0,0]], color: Color.Green }, // S + { shape: [[1,1,0],[0,1,1],[0,0,0]], color: Color.Red }, // Z + { shape: [[1,0,0],[1,1,1],[0,0,0]], color: Color.Blue }, // J + { shape: [[0,0,1],[1,1,1],[0,0,0]], color: Color.Orange }, // L +]; + +// 7-bag randomizer — ensures every 7 pieces appear once before repeating +let bag: number[] = []; + +function refillBag(): void { + bag = [0, 1, 2, 3, 4, 5, 6]; + // Fisher-Yates shuffle + for (let i = bag.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [bag[i], bag[j]] = [bag[j], bag[i]]; + } +} + +export function nextPieceIndex(): number { + if (bag.length === 0) refillBag(); + return bag.pop()!; +} + +export function makePiece(idx: number): Piece { + const def = SHAPES[idx]; + const shape = def.shape.map(row => [...row]); + const size = shape.length; + return { + shape, + color: def.color, + pos: { + x: Math.floor((10 - size) / 2), + y: 0, + }, + }; +} + +// Clockwise rotation: transpose then reverse rows +export function rotateCW(shape: Shape): Shape { + const n = shape.length; + const rotated: Shape = []; + for (let r = 0; r < n; r++) { + rotated.push([]); + for (let c = 0; c < n; c++) { + rotated[r].push(shape[n - 1 - c][r]); + } + } + return rotated; +} + +// Wall kick offsets to try when rotation causes collision +export const KICK_OFFSETS: Position[] = [ + { x: 0, y: 0 }, + { x: -1, y: 0 }, + { x: 1, y: 0 }, + { x: 0, y: -1 }, + { x: -1, y: -1 }, + { x: 1, y: -1 }, + { x: -2, y: 0 }, + { x: 2, y: 0 }, +]; diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/renderer.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/renderer.ts @@ -0,0 +1,360 @@ +// ── WebGL Renderer ── + +import { Color, RGBA, Grid } from "./types"; +import { VERTEX_SRC, FRAGMENT_SRC } from "./shaders"; +import { COLS, ROWS } from "./board"; + +const CELL = 30; // px per cell +const BOARD_PAD = 20; +const BOARD_W = COLS * CELL; +const BOARD_H = ROWS * CELL; +const SIDEBAR_W = 200; + +export const CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD; +export const CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD; + +// Color palette: index = Color enum value +const PALETTE: Record<number, RGBA> = { + [Color.Empty]: [0.08, 0.08, 0.12, 1], + [Color.Cyan]: [0.0, 0.9, 0.9, 1], + [Color.Yellow]: [0.95, 0.9, 0.1, 1], + [Color.Purple]: [0.7, 0.2, 0.9, 1], + [Color.Green]: [0.1, 0.9, 0.3, 1], + [Color.Red]: [0.9, 0.2, 0.2, 1], + [Color.Blue]: [0.2, 0.3, 0.9, 1], + [Color.Orange]: [0.95, 0.55, 0.1, 1], + [Color.Ghost]: [0.3, 0.3, 0.4, 0.35], +}; + +// Darker shade for the bevel/border effect +function darker(c: RGBA): RGBA { + return [c[0] * 0.55, c[1] * 0.55, c[2] * 0.55, c[3]]; +} +function lighter(c: RGBA): RGBA { + return [Math.min(1, c[0] * 1.35), Math.min(1, c[1] * 1.35), Math.min(1, c[2] * 1.35), c[3]]; +} + +interface GLResources { + gl: WebGLRenderingContext; + program: WebGLProgram; + aPosition: number; + aColor: number; + uResolution: WebGLUniformLocation | null; + buffer: WebGLBuffer; +} + +export function initGL(canvas: HTMLCanvasElement): GLResources { + const gl = canvas.getContext("webgl", { alpha: false })!; + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0.06, 0.06, 0.1, 1); + + function compile(type: number, src: string): WebGLShader { + const s = gl.createShader(type)!; + gl.shaderSource(s, src); + gl.compileShader(s); + if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { + throw new Error(gl.getShaderInfoLog(s)!); + } + return s; + } + + const vs = compile(gl.VERTEX_SHADER, VERTEX_SRC); + const fs = compile(gl.FRAGMENT_SHADER, FRAGMENT_SRC); + const prog = gl.createProgram()!; + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { + throw new Error(gl.getProgramInfoLog(prog)!); + } + + gl.useProgram(prog); + + return { + gl, + program: prog, + aPosition: gl.getAttribLocation(prog, "a_position"), + aColor: gl.getAttribLocation(prog, "a_color"), + uResolution: gl.getUniformLocation(prog, "u_resolution"), + buffer: gl.createBuffer()!, + }; +} + +// Push a colored quad (2 triangles = 6 vertices) into arrays +function pushQuad( + verts: number[], colors: number[], + x: number, y: number, w: number, h: number, + c: RGBA +) { + // Triangle 1 + verts.push(x, y, x+w, y, x, y+h); + // Triangle 2 + verts.push(x+w, y, x+w, y+h, x, y+h); + for (let i = 0; i < 6; i++) colors.push(...c); +} + +// Push a cell with a bevel/3D block look +function pushBlock( + verts: number[], colors: number[], + x: number, y: number, s: number, + c: RGBA +) { + const b = 2; // bevel width + // Base (darker border) + pushQuad(verts, colors, x, y, s, s, darker(c)); + // Top face + pushQuad(verts, colors, x + b, y + b, s - 2*b, s - 2*b, c); + // Highlight edge (top + left) + pushQuad(verts, colors, x + b, y + b, s - 2*b, 2, lighter(c)); + pushQuad(verts, colors, x + b, y + b, 2, s - 2*b, lighter(c)); +} + +export function render( + res: GLResources, + board: Grid, + currentShape: number[][] | null, + currentColor: Color | null, + currentPos: { x: number; y: number } | null, + ghostShape: number[][] | null, + ghostPos: { x: number; y: number } | null, + nextShape: number[][] | null, + nextColor: Color | null, + heldShape: number[][] | null, + heldColor: Color | null, + score: number, + level: number, + lines: number, + gameOver: boolean, + paused: boolean, +): void { + const { gl, aPosition, aColor, uResolution, buffer } = res; + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(uResolution, CANVAS_W, CANVAS_H); + + const verts: number[] = []; + const cols: number[] = []; + + // ── Board background ── + pushQuad(verts, cols, BOARD_PAD, BOARD_PAD, BOARD_W, BOARD_H, [0.05, 0.05, 0.08, 1]); + + // ── Grid lines ── + const gridColor: RGBA = [0.12, 0.12, 0.18, 1]; + for (let c = 0; c <= COLS; c++) { + const x = BOARD_PAD + c * CELL; + pushQuad(verts, cols, x, BOARD_PAD, 1, BOARD_H, gridColor); + } + for (let r = 0; r <= ROWS; r++) { + const y = BOARD_PAD + r * CELL; + pushQuad(verts, cols, BOARD_PAD, y, BOARD_W, 1, gridColor); + } + + // ── Locked board cells ── + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + if (board[r][c] !== Color.Empty) { + const px = BOARD_PAD + c * CELL; + const py = BOARD_PAD + r * CELL; + pushBlock(verts, cols, px, py, CELL, PALETTE[board[r][c]]); + } + } + } + + // ── Ghost piece ── + if (ghostShape && ghostPos) { + for (let r = 0; r < ghostShape.length; r++) { + for (let c = 0; c < ghostShape[r].length; c++) { + if (ghostShape[r][c] === 0) continue; + const gy = ghostPos.y + r; + const gx = ghostPos.x + c; + if (gy < 0) continue; + const px = BOARD_PAD + gx * CELL; + const py = BOARD_PAD + gy * CELL; + pushQuad(verts, cols, px + 1, py + 1, CELL - 2, CELL - 2, PALETTE[Color.Ghost]); + } + } + } + + // ── Current piece ── + if (currentShape && currentColor !== null && currentPos) { + const c = PALETTE[currentColor]; + for (let r = 0; r < currentShape.length; r++) { + for (let cc = 0; cc < currentShape[r].length; cc++) { + if (currentShape[r][cc] === 0) continue; + const py = currentPos.y + r; + const px = currentPos.x + cc; + if (py < 0) continue; + const bx = BOARD_PAD + px * CELL; + const by = BOARD_PAD + py * CELL; + pushBlock(verts, cols, bx, by, CELL, c); + } + } + } + + // ── Sidebar ── + const sx = BOARD_PAD + BOARD_W + BOARD_PAD; + + // Helper: draw text info using quads isn't practical, so we'll overlay with a 2D canvas. + // For now, draw sidebar panels as colored rects — we'll render text separately. + + // Next piece panel + const panelH = 130; + pushQuad(verts, cols, sx, BOARD_PAD, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (nextShape && nextColor !== null) { + const nc = PALETTE[nextColor]; + const size = nextShape.length; + const mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (size * mini) / 2; + const oy = BOARD_PAD + 30; + for (let r = 0; r < size; r++) { + for (let c = 0; c < size; c++) { + if (nextShape[r][c] === 0) continue; + pushBlock(verts, cols, ox + c * mini, oy + r * mini, mini, nc); + } + } + } + + // Hold piece panel + const holdY = BOARD_PAD + panelH + 15; + pushQuad(verts, cols, sx, holdY, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (heldShape && heldColor !== null) { + const hc = PALETTE[heldColor]; + const size = heldShape.length; + const mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (size * mini) / 2; + const oy = holdY + 30; + for (let r = 0; r < size; r++) { + for (let c = 0; c < size; c++) { + if (heldShape[r][c] === 0) continue; + pushBlock(verts, cols, ox + c * mini, oy + r * mini, mini, hc); + } + } + } + + // ── Upload & Draw ── + const vData = new Float32Array(verts.length + cols.length); + const stride = 6; // 2 pos + 4 color + for (let i = 0, vi = 0, ci = 0; i < vData.length; i += stride) { + vData[i] = verts[vi++]; + vData[i+1] = verts[vi++]; + vData[i+2] = cols[ci++]; + vData[i+3] = cols[ci++]; + vData[i+4] = cols[ci++]; + vData[i+5] = cols[ci++]; + } + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vData, gl.DYNAMIC_DRAW); + + const byteStride = stride * 4; + gl.enableVertexAttribArray(aPosition); + gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, byteStride, 0); + gl.enableVertexAttribArray(aColor); + gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, byteStride, 2 * 4); + + gl.drawArrays(gl.TRIANGLES, 0, vData.length / stride); + + // ── Text overlay via 2D context ── + drawTextOverlay( + canvas2d!, sx, BOARD_PAD, holdY, panelH, + score, level, lines, gameOver, paused + ); +} + +// We maintain a second hidden 2D canvas overlaid for text +let canvas2d: HTMLCanvasElement | null = null; +let ctx2d: CanvasRenderingContext2D | null = null; + +export function initTextOverlay(parent: HTMLElement): void { + canvas2d = document.createElement("canvas"); + canvas2d.width = CANVAS_W; + canvas2d.height = CANVAS_H; + canvas2d.style.position = "absolute"; + canvas2d.style.left = "0"; + canvas2d.style.top = "0"; + canvas2d.style.pointerEvents = "none"; + parent.style.position = "relative"; + parent.appendChild(canvas2d); + ctx2d = canvas2d.getContext("2d")!; +} + +function drawTextOverlay( + cv: HTMLCanvasElement, + sx: number, + topPad: number, + holdY: number, + panelH: number, + score: number, level: number, lines: number, + gameOver: boolean, paused: boolean +): void { + if (!ctx2d) return; + const ctx = ctx2d; + ctx.clearRect(0, 0, cv.width, cv.height); + ctx.font = "bold 14px 'Segoe UI', Arial, sans-serif"; + ctx.fillStyle = "#aaa"; + + // Next label + ctx.fillText("NEXT", sx + 8, topPad + 18); + // Hold label + ctx.fillText("HOLD", sx + 8, holdY + 18); + + // Score / Level / Lines + const infoY = holdY + panelH + 30; + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("SCORE", sx + 8, infoY); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(score.toLocaleString(), sx + 8, infoY + 24); + + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LEVEL", sx + 8, infoY + 54); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(level + 1), sx + 8, infoY + 78); + + ctx.fillStyle = "#ccc"; + ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LINES", sx + 8, infoY + 108); + ctx.fillStyle = "#fff"; + ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(lines), sx + 8, infoY + 132); + + // Controls help + const helpY = infoY + 172; + ctx.fillStyle = "#666"; + ctx.font = "12px 'Segoe UI', Arial, sans-serif"; + const controls = [ + "← → Move", + "↑ / Z Rotate", + "↓ Soft Drop", + "Space Hard Drop", + "C Hold", + "P Pause", + "R Restart", + ]; + controls.forEach((t, i) => ctx.fillText(t, sx + 8, helpY + i * 18)); + + // Overlays + if (gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 40, BOARD_W, 80); + ctx.fillStyle = "#ff4444"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.textAlign = "center"; + ctx.fillText("GAME OVER", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.fillStyle = "#ccc"; + ctx.font = "16px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("Press R to restart", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 28); + ctx.textAlign = "left"; + } + if (paused && !gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 30, BOARD_W, 60); + ctx.fillStyle = "#ffdd44"; + ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.textAlign = "center"; + ctx.fillText("PAUSED", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.textAlign = "left"; + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/shaders.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/shaders.ts @@ -0,0 +1,26 @@ +// ── GLSL Shaders ── + +export const VERTEX_SRC = ` +attribute vec2 a_position; +attribute vec4 a_color; + +uniform vec2 u_resolution; + +varying vec4 v_color; + +void main() { + // Convert pixel coords to clip space + vec2 clip = (a_position / u_resolution) * 2.0 - 1.0; + gl_Position = vec4(clip * vec2(1, -1), 0, 1); + v_color = a_color; +} +`; + +export const FRAGMENT_SRC = ` +precision mediump float; +varying vec4 v_color; + +void main() { + gl_FragColor = v_color; +} +`; diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/tetris.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/tetris.ts @@ -0,0 +1,525 @@ +// ══════════════════════════════════════════════════════════ +// TETRIS — TypeScript + WebGL +// Single-file implementation +// ══════════════════════════════════════════════════════════ + +// ── Types ── + +enum Color { + Empty = 0, + Cyan, // I + Yellow, // O + Purple, // T + Green, // S + Red, // Z + Blue, // J + Orange, // L +} + +type RGBA = [number, number, number, number]; +type Shape = number[][]; + +interface Position { x: number; y: number } + +interface Piece { + shape: Shape; + color: Color; + pos: Position; +} + +// ── Constants ── + +const COLS = 10; +const ROWS = 20; +const CELL = 30; +const BOARD_PAD = 20; +const BOARD_W = COLS * CELL; +const BOARD_H = ROWS * CELL; +const SIDEBAR_W = 200; +const CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD; +const CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD; + +const PALETTE: Record<number, RGBA> = { + [Color.Empty]: [0.08, 0.08, 0.12, 1], + [Color.Cyan]: [0.0, 0.9, 0.9, 1], + [Color.Yellow]: [0.95, 0.9, 0.1, 1], + [Color.Purple]: [0.7, 0.2, 0.9, 1], + [Color.Green]: [0.1, 0.9, 0.3, 1], + [Color.Red]: [0.9, 0.2, 0.2, 1], + [Color.Blue]: [0.2, 0.3, 0.9, 1], + [Color.Orange]: [0.95, 0.55, 0.1, 1], +}; + +const SHAPES: { shape: Shape; color: Color }[] = [ + { shape: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], color: Color.Cyan }, + { shape: [[1,1],[1,1]], color: Color.Yellow }, + { shape: [[0,1,0],[1,1,1],[0,0,0]], color: Color.Purple }, + { shape: [[0,1,1],[1,1,0],[0,0,0]], color: Color.Green }, + { shape: [[1,1,0],[0,1,1],[0,0,0]], color: Color.Red }, + { shape: [[1,0,0],[1,1,1],[0,0,0]], color: Color.Blue }, + { shape: [[0,0,1],[1,1,1],[0,0,0]], color: Color.Orange }, +]; + +const KICK_OFFSETS: Position[] = [ + { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 1, y: 0 }, + { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 }, + { x: -2, y: 0 }, { x: 2, y: 0 }, +]; + +// ── Piece Logic ── + +let bag: number[] = []; + +function refillBag(): void { + bag = [0, 1, 2, 3, 4, 5, 6]; + for (let i = bag.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [bag[i], bag[j]] = [bag[j], bag[i]]; + } +} + +function nextIdx(): number { + if (bag.length === 0) refillBag(); + return bag.pop()!; +} + +function makePiece(idx: number): Piece { + const def = SHAPES[idx]; + const shape = def.shape.map(r => [...r]); + return { shape, color: def.color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} + +function makePieceFrom(shape: Shape, color: Color): Piece { + return { shape: shape.map(r => [...r]), color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } }; +} + +function rotateCW(shape: Shape): Shape { + const n = shape.length; + const out: Shape = []; + for (let r = 0; r < n; r++) { + out.push([]); + for (let c = 0; c < n; c++) out[r].push(shape[n - 1 - c][r]); + } + return out; +} + +// ── Board Logic ── + +type Grid = Color[][]; + +function createGrid(): Grid { + const g: Grid = []; + for (let r = 0; r < ROWS; r++) g.push(new Array<Color>(COLS).fill(Color.Empty)); + return g; +} + +function collides(grid: Grid, shape: Shape, pos: Position): boolean { + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) continue; + const nx = pos.x + c, ny = pos.y + r; + if (nx < 0 || nx >= COLS || ny >= ROWS) return true; + if (ny >= 0 && grid[ny][nx] !== Color.Empty) return true; + } + return false; +} + +function lock(grid: Grid, shape: Shape, pos: Position, color: Color): Grid { + const g = grid.map(r => [...r]); + for (let r = 0; r < shape.length; r++) + for (let c = 0; c < shape[r].length; c++) { + if (!shape[r][c]) continue; + const ny = pos.y + r, nx = pos.x + c; + if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) g[ny][nx] = color; + } + return g; +} + +function clearLines(grid: Grid): { grid: Grid; cleared: number } { + const remaining = grid.filter(row => row.some(c => c === Color.Empty)); + const cleared = ROWS - remaining.length; + while (remaining.length < ROWS) remaining.unshift(new Array<Color>(COLS).fill(Color.Empty)); + return { grid: remaining, cleared }; +} + +function ghostY(grid: Grid, shape: Shape, pos: Position): number { + let gy = pos.y; + while (!collides(grid, shape, { x: pos.x, y: gy + 1 })) gy++; + return gy; +} + +function calcScore(n: number, lvl: number): number { + return [0, 40, 100, 300, 1200][n] * (lvl + 1); +} + +function gravityMs(lvl: number): number { + const s = [800,720,630,550,470,380,300,220,140,100,80,80,80,70,70,70,50,50,50,30]; + return s[Math.min(lvl, s.length - 1)]; +} + +// ── Shaders ── + +const VERT = ` +attribute vec2 a_pos; +attribute vec4 a_col; +uniform vec2 u_res; +varying vec4 v_col; +void main() { + vec2 clip = (a_pos / u_res) * 2.0 - 1.0; + gl_Position = vec4(clip * vec2(1, -1), 0, 1); + v_col = a_col; +}`; + +const FRAG = ` +precision mediump float; +varying vec4 v_col; +void main() { gl_FragColor = v_col; }`; + +// ── WebGL Renderer ── + +let gl: WebGLRenderingContext; +let prog: WebGLProgram; +let aPos: number, aCol: number, uRes: WebGLUniformLocation | null; +let buf: WebGLBuffer; + +function initGL(canvas: HTMLCanvasElement): void { + gl = canvas.getContext("webgl", { alpha: false })!; + gl.viewport(0, 0, canvas.width, canvas.height); + gl.clearColor(0.06, 0.06, 0.1, 1); + + function comp(type: number, src: string): WebGLShader { + const s = gl.createShader(type)!; + gl.shaderSource(s, src); + gl.compileShader(s); + return s; + } + const vs = comp(gl.VERTEX_SHADER, VERT); + const fs = comp(gl.FRAGMENT_SHADER, FRAG); + prog = gl.createProgram()!; + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + gl.useProgram(prog); + + aPos = gl.getAttribLocation(prog, "a_pos"); + aCol = gl.getAttribLocation(prog, "a_col"); + uRes = gl.getUniformLocation(prog, "u_res"); + buf = gl.createBuffer()!; +} + +function darker(c: RGBA): RGBA { return [c[0]*0.55, c[1]*0.55, c[2]*0.55, c[3]]; } +function lighter(c: RGBA): RGBA { return [Math.min(1,c[0]*1.35), Math.min(1,c[1]*1.35), Math.min(1,c[2]*1.35), c[3]]; } + +function pushQuad(v: number[], cl: number[], x: number, y: number, w: number, h: number, c: RGBA): void { + v.push(x,y, x+w,y, x,y+h, x+w,y, x+w,y+h, x,y+h); + for (let i = 0; i < 6; i++) cl.push(...c); +} + +function pushBlock(v: number[], cl: number[], x: number, y: number, s: number, c: RGBA): void { + const b = 2; + pushQuad(v, cl, x, y, s, s, darker(c)); + pushQuad(v, cl, x+b, y+b, s-2*b, s-2*b, c); + pushQuad(v, cl, x+b, y+b, s-2*b, b, lighter(c)); + pushQuad(v, cl, x+b, y+b, b, s-2*b, lighter(c)); +} + +// ── State ── + +let board: Grid; +let cur: Piece | null; +let nxt: Piece | null; +let held: Piece | null; +let canHold: boolean; +let score: number, level: number, linesCleared: number; +let gameOver: boolean, paused: boolean; +let lastTick: number; +let softDrop: boolean; + +function resetGame(): void { + board = createGrid(); + cur = makePiece(nextIdx()); + nxt = makePiece(nextIdx()); + held = null; + canHold = true; + score = 0; level = 0; linesCleared = 0; + gameOver = false; paused = false; + lastTick = performance.now(); + softDrop = false; + for (const k of Object.keys(keyTimers)) { clearTimeout(keyTimers[k]); clearInterval(keyTimers[k]); } + keyTimers = {}; +} + +// ── Game Actions ── + +function move(dx: number, dy: number): boolean { + if (!cur) return false; + const p = { x: cur.pos.x + dx, y: cur.pos.y + dy }; + if (!collides(board, cur.shape, p)) { cur.pos = p; return true; } + return false; +} + +function tryRotate(): void { + if (!cur) return; + const rot = rotateCW(cur.shape); + for (const k of KICK_OFFSETS) { + const p = { x: cur.pos.x + k.x, y: cur.pos.y + k.y }; + if (!collides(board, rot, p)) { cur.shape = rot; cur.pos = p; return; } + } +} + +function hardDrop(): void { + if (!cur) return; + const gy = ghostY(board, cur.shape, cur.pos); + score += (gy - cur.pos.y) * 2; + cur.pos.y = gy; + lockAndAdvance(); +} + +function lockAndAdvance(): void { + if (!cur) return; + board = lock(board, cur.shape, cur.pos, cur.color); + const { grid: nb, cleared } = clearLines(board); + board = nb; + if (cleared) { linesCleared += cleared; score += calcScore(cleared, level); level = Math.floor(linesCleared / 10); } + cur = nxt; + nxt = makePiece(nextIdx()); + canHold = true; + if (cur && collides(board, cur.shape, cur.pos)) gameOver = true; +} + +function doHold(): void { + if (!cur || !canHold) return; + canHold = false; + const cc = cur.color, cs = cur.shape.map(r => [...r]); + if (held) { + const hc = held.color, hs = held.shape; + cur = makePieceFrom(hs, hc); + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + } else { + held = { shape: cs, color: cc, pos: { x: 0, y: 0 } }; + cur = nxt; + nxt = makePiece(nextIdx()); + } +} + +// ── Input ── + +let keyTimers: Record<string, number> = {}; +const DAS = 170, ARR = 50; + +function startRepeat(code: string, fn: () => void): void { + stopRepeat(code); + keyTimers[code + "_d"] = window.setTimeout(() => { keyTimers[code] = window.setInterval(fn, ARR); }, DAS); +} + +function stopRepeat(code: string): void { + clearTimeout(keyTimers[code + "_d"]); clearInterval(keyTimers[code]); + delete keyTimers[code + "_d"]; delete keyTimers[code]; +} + +function onKeyDown(e: KeyboardEvent): void { + if (e.repeat) return; + switch (e.code) { + case "ArrowLeft": e.preventDefault(); move(-1,0); startRepeat("ArrowLeft", ()=>move(-1,0)); break; + case "ArrowRight": e.preventDefault(); move(1,0); startRepeat("ArrowRight",()=>move(1,0)); break; + case "ArrowDown": e.preventDefault(); softDrop = true; break; + case "ArrowUp": case "KeyX": e.preventDefault(); tryRotate(); break; + case "KeyZ": e.preventDefault(); tryRotate(); tryRotate(); tryRotate(); break; + case "Space": e.preventDefault(); hardDrop(); break; + case "KeyC": case "ShiftLeft": case "ShiftRight": e.preventDefault(); doHold(); break; + case "KeyP": case "Escape": paused = !paused; break; + case "KeyR": resetGame(); break; + } +} + +function onKeyUp(e: KeyboardEvent): void { + if (e.code === "ArrowLeft" || e.code === "ArrowRight") stopRepeat(e.code); + if (e.code === "ArrowDown") softDrop = false; +} + +// ── Render Frame ── + +let canvas2d: HTMLCanvasElement, ctx2d: CanvasRenderingContext2D; + +function drawFrame(): void { + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform2f(uRes, CANVAS_W, CANVAS_H); + + const v: number[] = [], cl: number[] = []; + + // Board background + pushQuad(v, cl, BOARD_PAD, BOARD_PAD, BOARD_W, BOARD_H, [0.05, 0.05, 0.08, 1]); + + // Grid lines + const gc: RGBA = [0.12, 0.12, 0.18, 1]; + for (let c = 0; c <= COLS; c++) pushQuad(v, cl, BOARD_PAD + c * CELL, BOARD_PAD, 1, BOARD_H, gc); + for (let r = 0; r <= ROWS; r++) pushQuad(v, cl, BOARD_PAD, BOARD_PAD + r * CELL, BOARD_W, 1, gc); + + // Locked cells + for (let r = 0; r < ROWS; r++) + for (let c = 0; c < COLS; c++) + if (board[r][c] !== Color.Empty) + pushBlock(v, cl, BOARD_PAD + c * CELL, BOARD_PAD + r * CELL, CELL, PALETTE[board[r][c]]); + + // Ghost + if (cur) { + const gy = ghostY(board, cur.shape, cur.pos); + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) continue; + const py = gy + r; + if (py < 0) continue; + pushQuad(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL + 1, BOARD_PAD + py * CELL + 1, CELL - 2, CELL - 2, [0.3, 0.3, 0.4, 0.35]); + } + + // Current piece + for (let r = 0; r < cur.shape.length; r++) + for (let c = 0; c < cur.shape[r].length; c++) { + if (!cur.shape[r][c]) continue; + const py = cur.pos.y + r; + if (py < 0) continue; + pushBlock(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL, BOARD_PAD + py * CELL, CELL, PALETTE[cur.color]); + } + } + + // Sidebar + const sx = BOARD_PAD + BOARD_W + BOARD_PAD; + const panelH = 130; + + // Next panel + pushQuad(v, cl, sx, BOARD_PAD, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (nxt) { + const sz = nxt.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = BOARD_PAD + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (nxt.shape[r][c]) pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[nxt.color]); + } + + // Hold panel + const holdY = BOARD_PAD + panelH + 15; + pushQuad(v, cl, sx, holdY, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]); + if (held) { + const sz = held.shape.length, mini = 22; + const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2; + const oy = holdY + 30; + for (let r = 0; r < sz; r++) + for (let c = 0; c < sz; c++) + if (held.shape[r][c]) pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[held.color]); + } + + // Upload and draw + const stride = 6; + const data = new Float32Array(v.length + cl.length); + for (let i = 0, vi = 0, ci = 0; i < data.length; i += stride) { + data[i] = v[vi++]; data[i+1] = v[vi++]; + data[i+2] = cl[ci++]; data[i+3] = cl[ci++]; data[i+4] = cl[ci++]; data[i+5] = cl[ci++]; + } + gl.bindBuffer(gl.ARRAY_BUFFER, buf); + gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW); + const bs = stride * 4; + gl.enableVertexAttribArray(aPos); + gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, bs, 0); + gl.enableVertexAttribArray(aCol); + gl.vertexAttribPointer(aCol, 4, gl.FLOAT, false, bs, 8); + gl.drawArrays(gl.TRIANGLES, 0, data.length / stride); + + // 2D text overlay + const ctx = ctx2d; + ctx.clearRect(0, 0, CANVAS_W, CANVAS_H); + ctx.font = "bold 14px 'Segoe UI', Arial, sans-serif"; + ctx.fillStyle = "#aaa"; + ctx.textAlign = "left"; + ctx.fillText("NEXT", sx + 8, BOARD_PAD + 18); + ctx.fillText("HOLD", sx + 8, holdY + 18); + + const iy = holdY + panelH + 30; + ctx.fillStyle = "#ccc"; ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("SCORE", sx + 8, iy); + ctx.fillStyle = "#fff"; ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(score.toLocaleString(), sx + 8, iy + 24); + + ctx.fillStyle = "#ccc"; ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LEVEL", sx + 8, iy + 54); + ctx.fillStyle = "#fff"; ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(level + 1), sx + 8, iy + 78); + + ctx.fillStyle = "#ccc"; ctx.font = "bold 13px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("LINES", sx + 8, iy + 108); + ctx.fillStyle = "#fff"; ctx.font = "bold 22px 'Segoe UI', Arial, sans-serif"; + ctx.fillText(String(linesCleared), sx + 8, iy + 132); + + ctx.fillStyle = "#666"; ctx.font = "12px 'Segoe UI', Arial, sans-serif"; + const help = ["← → Move", "↑ / X Rotate CW", "Z Rotate CCW", "↓ Soft Drop", "Space Hard Drop", "C Hold", "P Pause", "R Restart"]; + help.forEach((t, i) => ctx.fillText(t, sx + 8, iy + 170 + i * 18)); + + if (gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.7)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 40, BOARD_W, 80); + ctx.textAlign = "center"; + ctx.fillStyle = "#ff4444"; ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("GAME OVER", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.fillStyle = "#ccc"; ctx.font = "16px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("Press R to restart", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 28); + ctx.textAlign = "left"; + } + if (paused && !gameOver) { + ctx.fillStyle = "rgba(0,0,0,0.6)"; + ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 30, BOARD_W, 60); + ctx.textAlign = "center"; + ctx.fillStyle = "#ffdd44"; ctx.font = "bold 28px 'Segoe UI', Arial, sans-serif"; + ctx.fillText("PAUSED", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4); + ctx.textAlign = "left"; + } +} + +// ── Main Loop ── + +function tick(now: number): void { + requestAnimationFrame(tick); + if (gameOver || paused) { drawFrame(); return; } + + const interval = softDrop ? Math.min(gravityMs(level), 50) : gravityMs(level); + if (now - lastTick >= interval) { + lastTick = now; + if (softDrop) score += 1; + if (!move(0, 1)) lockAndAdvance(); + } + drawFrame(); +} + +// ── Bootstrap ── + +function start(): void { + const wrapper = document.getElementById("game-wrapper")!; + + const canvas = document.createElement("canvas"); + canvas.width = CANVAS_W; canvas.height = CANVAS_H; + canvas.style.display = "block"; canvas.style.borderRadius = "4px"; + canvas.tabIndex = 0; + wrapper.appendChild(canvas); + + canvas2d = document.createElement("canvas"); + canvas2d.width = CANVAS_W; canvas2d.height = CANVAS_H; + canvas2d.style.position = "absolute"; canvas2d.style.left = "0"; canvas2d.style.top = "0"; + canvas2d.style.pointerEvents = "none"; + wrapper.style.position = "relative"; + wrapper.appendChild(canvas2d); + ctx2d = canvas2d.getContext("2d")!; + + initGL(canvas); + resetGame(); + + canvas.addEventListener("keydown", onKeyDown); + canvas.addEventListener("keyup", onKeyUp); + canvas.focus(); + + requestAnimationFrame(tick); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", start); +} else { + start(); +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/types.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/src/types.ts @@ -0,0 +1,44 @@ +// ── Core Types ── + +export enum Color { + Empty = 0, + Cyan, // I + Yellow, // O + Purple, // T + Green, // S + Red, // Z + Blue, // J + Orange, // L + Ghost, // preview of where piece will land +} + +export interface Position { + x: number; + y: number; +} + +export type Shape = number[][]; + +export interface Piece { + shape: Shape; + color: Color; + pos: Position; +} + +export type Grid = Color[][]; + +export interface GameState { + board: Grid; + current: Piece | null; + next: Piece | null; + held: Piece | null; + canHold: boolean; + score: number; + level: number; + lines: number; + gameOver: boolean; + paused: boolean; +} + +// RGBA as [r, g, b, a] in 0..1 +export type RGBA = [number, number, number, number]; diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/tsconfig.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/tetris/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "strict": true, + "noImplicitAny": true, + "removeComments": true, + "sourceMap": false, + "rootDir": "./src", + "outDir": "../build", + "lib": ["ES2020", "DOM", "DOM.Iterable"] + }, + "files": ["src/tetris.ts"] +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package-lock.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package-lock.json @@ -0,0 +1,2270 @@ +{ + "name": "loop-bench-xnwdswui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "loop-bench-xnwdswui", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.4.tgz", + "integrity": "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.4", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.4.tgz", + "integrity": "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", + "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", + "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", + "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jscpd/badge-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/badge-reporter/-/badge-reporter-4.0.4.tgz", + "integrity": "sha512-I9b4MmLXPM2vo0SxSUWnNGKcA4PjQlD3GzXvFK60z43cN/EIdLbOq3FVwCL+dg2obUqGXKIzAm7EsDFTg0D+mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "badgen": "^3.2.3", + "colors": "^1.4.0", + "fs-extra": "^11.2.0" + } + }, + "node_modules/@jscpd/core": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/core/-/core-4.0.4.tgz", + "integrity": "sha512-QGMT3iXEX1fI6lgjPH+x8eyJwhwr2KkpSF5uBpjC0Z5Xloj0yFTFLtwJT+RhxP/Ob4WYrtx2jvpKB269oIwgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1" + } + }, + "node_modules/@jscpd/finder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/finder/-/finder-4.0.4.tgz", + "integrity": "sha512-qVUWY7Nzuvfd5OIk+n7/5CM98LmFroLqblRXAI2gDABwZrc7qS+WH2SNr0qoUq0f4OqwM+piiwKvwL/VDNn/Cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "blamer": "^1.0.6", + "bytes": "^3.1.2", + "cli-table3": "^0.6.5", + "colors": "^1.4.0", + "fast-glob": "^3.3.2", + "fs-extra": "^11.2.0", + "markdown-table": "^2.0.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/html-reporter": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/html-reporter/-/html-reporter-4.0.4.tgz", + "integrity": "sha512-YiepyeYkeH74Kx59PJRdUdonznct0wHPFkf6FLQN+mCBoy6leAWCcOfHtcexnp+UsBFDlItG5nRdKrDSxSH+Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "1.4.0", + "fs-extra": "^11.2.0", + "pug": "^3.0.3" + } + }, + "node_modules/@jscpd/tokenizer": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@jscpd/tokenizer/-/tokenizer-4.0.4.tgz", + "integrity": "sha512-xxYYY/qaLah/FlwogEbGIxx9CjDO+G9E6qawcy26WwrflzJb6wsnhjwdneN6Wb0RNCDsqvzY+bzG453jsin4UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/core": "4.0.4", + "reprism": "^0.0.11", + "spark-md5": "^3.0.2" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz", + "integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert-never": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.4.0.tgz", + "integrity": "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/badgen": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/badgen/-/badgen-3.2.3.tgz", + "integrity": "sha512-svDuwkc63E/z0ky3drpUppB83s/nlgDciH9m+STwwQoWyq7yCgew1qEfJ+9axkKdNq7MskByptWUN9j1PGMwFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/blamer": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/blamer/-/blamer-1.0.7.tgz", + "integrity": "sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^4.0.0", + "which": "^2.0.2" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gitignore-to-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gitignore-to-glob/-/gitignore-to-glob-0.3.0.tgz", + "integrity": "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.4 <5 || >=6.9" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jscpd": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/jscpd/-/jscpd-4.0.8.tgz", + "integrity": "sha512-d2VNT/2Hv4dxT2/59He8Lyda4DYOxPRyRG9zBaOpTZAqJCVf2xLrBlZkT8Va6Lo9u3X2qz8Bpq4HrDi4JsrQhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jscpd/badge-reporter": "4.0.4", + "@jscpd/core": "4.0.4", + "@jscpd/finder": "4.0.4", + "@jscpd/html-reporter": "4.0.4", + "@jscpd/tokenizer": "4.0.4", + "colors": "^1.4.0", + "commander": "^5.0.0", + "fs-extra": "^11.2.0", + "gitignore-to-glob": "^0.3.0", + "jscpd-sarif-reporter": "4.0.6" + }, + "bin": { + "jscpd": "bin/jscpd" + } + }, + "node_modules/jscpd-sarif-reporter": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/jscpd-sarif-reporter/-/jscpd-sarif-reporter-4.0.6.tgz", + "integrity": "sha512-b9Sm3IPZ3+m8Lwa4gZa+4/LhDhlc/ZLEsLXKSOy1DANQ6kx0ueqZT+fUHWEdQ6m0o3+RIVIa7DmvLSojQD05ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "fs-extra": "^11.2.0", + "node-sarif-builder": "^3.4.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-sarif-builder": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.4.0.tgz", + "integrity": "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "asap": "~2.0.3" + } + }, + "node_modules/pug": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.4.tgz", + "integrity": "sha512-kFfq5mMzrS7+wrl5pLJzZEzemx34OQ0w4SARfhy/3yxTlhbstsudDwJzhf1hP02yHzbjoVMSXUj/Sz6RNfMyXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-code-gen": "^3.0.4", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.4.tgz", + "integrity": "sha512-6okWYIKdasTyXICyEtvobmTZAVX57JkzgzIi4iRJlin8kmhG+Xry2dsus+Mun/nGCn6F2U49haHI5mkELXB14g==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "dev": true, + "license": "MIT" + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/reprism": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/reprism/-/reprism-0.0.11.tgz", + "integrity": "sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/spark-md5": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.2.tgz", + "integrity": "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "dev": true, + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package.json b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/package.json @@ -0,0 +1,20 @@ +{ + "name": "loop-bench-xnwdswui", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/node": "^25.5.2", + "eslint": "^10.2.0", + "jscpd": "^4.0.8", + "typescript": "^6.0.2" + } +} diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 30_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-few/tetris.spec.ts @@ -0,0 +1,96 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + }); + + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Give the page a moment to finish initializing + await page.waitForTimeout(2000); + + expect(errors).toEqual([]); + }); + + test("game board is visible", async ({ page }) => { + // A Tetris game should render either a <canvas> or a grid of DOM elements + const canvas = page.locator("canvas"); + const gridContainer = page.locator( + [ + '[class*="board"]', + '[class*="grid"]', + '[class*="game"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); + + const canvasCount = await canvas.count(); + const gridCount = await gridContainer.count(); + + expect( + canvasCount + gridCount, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + }); + + test("keyboard input does not crash the game", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Press every key a Tetris game should handle + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + for (const key of keys) { + await page.keyboard.press(key); + await page.waitForTimeout(150); + } + + expect(errors).toEqual([]); + }); + + test("game state changes over time", async ({ page }) => { + // If the game is running, the visual output should change as pieces fall + const shot1 = await page.screenshot(); + await page.waitForTimeout(3000); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the page to visually change over 3 seconds (pieces should be falling)" + ).toBe(false); + }); +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/playwright.config.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/playwright.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: ".", + timeout: 60_000, + retries: 0, + workers: 1, + use: { + baseURL: "http://localhost:3000", + headless: true, + viewport: { width: 1280, height: 720 }, + }, +}); diff --git a/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/tetris.spec.ts b/artifacts/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/tests-full/tetris.spec.ts @@ -0,0 +1,474 @@ +import { test, expect, type Page } from "@playwright/test"; + +// Try common entry points until one loads successfully. +async function loadGame(page: Page) { + const candidates = [ + "/", + "/index.html", + "/dist/index.html", + "/public/index.html", + "/build/index.html", + ]; + + for (const path of candidates) { + try { + const resp = await page.goto(path, { timeout: 5000 }); + if (resp?.ok()) return; + } catch { + continue; + } + } +} + +// Find the game surface: canvas or a grid-like DOM container. +function gameBoard(page: Page) { + return page.locator( + [ + "canvas", + '[class*="board"]', + '[class*="grid"]', + '[class*="game-area"]', + '[class*="field"]', + '[id*="board"]', + '[id*="grid"]', + '[id*="game"]', + '[id*="field"]', + "table", + ].join(", ") + ); +} + +// Click the board area to make sure it has focus, then try common +// start interactions in case the game waits for user action. +async function ensureGameStarted(page: Page) { + const board = gameBoard(page); + const count = await board.count(); + if (count > 0) { + try { + await board.first().click({ timeout: 2000 }); + } catch { + // click failed, continue anyway + } + } + + // Some games need a key press or button click to start + const startButton = page.locator( + 'button:has-text("start"), button:has-text("Start"), button:has-text("play"), button:has-text("Play"), [class*="start"], [id*="start"]' + ); + if ((await startButton.count()) > 0) { + try { + await startButton.first().click({ timeout: 2000 }); + } catch { + // ignore + } + } + + // Press Enter/Space as a fallback start trigger + await page.keyboard.press("Enter"); + await page.waitForTimeout(300); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); +} + +test.describe("Tetris Game", () => { + test.beforeEach(async ({ page }) => { + await loadGame(page); + await page.waitForLoadState("domcontentloaded"); + await page.waitForTimeout(1000); + await ensureGameStarted(page); + }); + + // ---- 1. Page loads without errors ---- + test("page loads without console errors", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + await page.waitForTimeout(2000); + expect(errors).toEqual([]); + }); + + // ---- 2. Game board is visible ---- + test("game board is visible", async ({ page }) => { + const board = gameBoard(page); + const count = await board.count(); + + expect( + count, + "Expected a <canvas> or a container with board/grid/game/field in its class or id" + ).toBeGreaterThan(0); + + // The board element should have meaningful dimensions + const box = await board.first().boundingBox(); + expect(box, "Game board should be visible on screen").not.toBeNull(); + expect(box!.width).toBeGreaterThan(50); + expect(box!.height).toBeGreaterThan(50); + }); + + // ---- 3. Game starts automatically or via interaction ---- + test("game starts", async ({ page }) => { + // After beforeEach, the game should be running. Verify by checking that + // the page is not static: take two screenshots separated by time. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected the game to show visual activity after starting" + ).toBe(false); + }); + + // ---- 4. Piece falls automatically (auto-drop) ---- + test("piece falls automatically", async ({ page }) => { + // Take screenshots at intervals without pressing any keys. + // A falling piece should cause visual changes. + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot3 = await page.screenshot(); + + const buf1 = Buffer.from(shot1); + const buf2 = Buffer.from(shot2); + const buf3 = Buffer.from(shot3); + + // At least one pair should differ (piece is moving down) + const changed = !buf1.equals(buf2) || !buf2.equals(buf3); + expect(changed, "Expected piece to fall over time without input").toBe( + true + ); + }); + + // ---- 5. Left arrow moves piece left ---- + test("left arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + // The piece should have moved, so the screenshots should differ + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing left arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 6. Right arrow moves piece right ---- + test("right arrow moves piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(200); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing right arrow" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 7. Down arrow moves piece down faster ---- + test("down arrow accelerates piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + for (let i = 0; i < 10; i++) { + await page.keyboard.press("ArrowDown"); + await page.waitForTimeout(50); + } + await page.waitForTimeout(200); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing down arrow repeatedly" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 8. Up arrow (or Z) rotates piece ---- + test("rotation changes the piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(300); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing rotate key" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 9. Space bar hard-drops piece ---- + test("space bar hard-drops piece", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + const shot1 = await page.screenshot(); + await page.keyboard.press("Space"); + await page.waitForTimeout(500); + const shot2 = await page.screenshot(); + + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected visual change after pressing space (hard drop)" + ).toBe(false); + expect(errors).toEqual([]); + }); + + // ---- 10. Pieces lock at the bottom ---- + test("pieces lock at the bottom", async ({ page }) => { + // Hard-drop several pieces and check that the bottom of the board + // accumulates filled cells (the visual should change cumulatively). + const shots: Buffer[] = []; + + shots.push(Buffer.from(await page.screenshot())); + + for (let i = 0; i < 3; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(800); + } + + shots.push(Buffer.from(await page.screenshot())); + + // After 3 hard drops, the board should look different from the start + // because pieces have stacked up at the bottom. + expect( + shots[0].equals(shots[1]), + "Expected pieces to stack up at the bottom after hard drops" + ).toBe(false); + }); + + // ---- 11. New piece spawns after lock ---- + test("new piece spawns after locking", async ({ page }) => { + // Hard-drop to lock a piece, then wait and verify the game is still + // showing activity (a new piece should be falling). + await page.keyboard.press("Space"); + await page.waitForTimeout(1000); + + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + + // If a new piece spawned and is falling, the screen should change + expect( + Buffer.from(shot1).equals(Buffer.from(shot2)), + "Expected a new piece to spawn and fall after the previous one locked" + ).toBe(false); + }); + + // ---- 12. Multiple different pieces appear ---- + test("multiple different pieces appear", async ({ page }) => { + // Play through several pieces and capture screenshots. Different piece + // shapes should produce visually distinct patterns. + const shots: Buffer[] = []; + + for (let i = 0; i < 6; i++) { + // Move each piece to a different column so they don't overlap identically + if (i % 2 === 0) { + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(100); + } else { + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(100); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(600); + shots.push(Buffer.from(await page.screenshot())); + } + + // At least some consecutive screenshots should differ (different piece shapes) + let differences = 0; + for (let i = 1; i < shots.length; i++) { + if (!shots[i - 1].equals(shots[i])) differences++; + } + + expect( + differences, + "Expected to see visual differences between consecutive pieces (different shapes)" + ).toBeGreaterThanOrEqual(2); + }); + + // ---- 13. Completed line clears ---- + test("completed line clears", async ({ page }) => { + // Fill a row by dropping many pieces. Observe whether any row disappears. + // We can detect this by tracking the total filled area -- after a line clear, + // the board should have less filled content than just before the clear. + const pageText = async () => + (await page.evaluate(() => document.body.innerText)) || ""; + + // Drop many pieces rapidly to fill rows + for (let i = 0; i < 30; i++) { + // Vary positions to try to complete a row + const moves = (i % 5) - 2; // -2, -1, 0, 1, 2 + for (let m = 0; m < Math.abs(moves); m++) { + await page.keyboard.press( + moves < 0 ? "ArrowLeft" : "ArrowRight" + ); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + // Check if a score or lines counter changed (common indicators of line clears) + const text = await pageText(); + const numbers = (text.match(/\d+/g) || []).map(Number); + const hasNonZero = numbers.some((n) => n > 0); + + // Also check visual: the board should not be completely static + const shot1 = await page.screenshot(); + await page.waitForTimeout(1000); + const shot2 = await page.screenshot(); + + // Either: score/lines increased, or game is still active (meaning lines cleared + // and made room for more pieces instead of game over) + const stillActive = !Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + hasNonZero || stillActive, + "Expected evidence of line clearing (score > 0 or game still active after many drops)" + ).toBe(true); + }); + + // ---- 14. Score increases during play ---- + test("score increases during play", async ({ page }) => { + // Look for a score display on the page + const getNumbers = async () => { + const text = (await page.evaluate(() => document.body.innerText)) || ""; + return (text.match(/\d+/g) || []).map(Number); + }; + + const numbersBefore = await getNumbers(); + + // Play for a while: drop several pieces + for (let i = 0; i < 15; i++) { + const offset = (i % 5) - 2; + for (let m = 0; m < Math.abs(offset); m++) { + await page.keyboard.press(offset < 0 ? "ArrowLeft" : "ArrowRight"); + await page.waitForTimeout(50); + } + await page.keyboard.press("Space"); + await page.waitForTimeout(300); + } + + const numbersAfter = await getNumbers(); + + // At least one number on the page should have increased + // (score, lines counter, level, etc.) + let anyIncreased = false; + const maxLen = Math.min(numbersBefore.length, numbersAfter.length); + for (let i = 0; i < maxLen; i++) { + if (numbersAfter[i] > numbersBefore[i]) { + anyIncreased = true; + break; + } + } + + // Also accept if new numbers appeared + if (!anyIncreased && numbersAfter.length > numbersBefore.length) { + anyIncreased = true; + } + + // Also accept if the max number increased + if (!anyIncreased) { + const maxBefore = numbersBefore.length > 0 ? Math.max(...numbersBefore) : 0; + const maxAfter = numbersAfter.length > 0 ? Math.max(...numbersAfter) : 0; + if (maxAfter > maxBefore) anyIncreased = true; + } + + expect( + anyIncreased, + "Expected at least one numeric value on the page to increase during play (score, lines, level)" + ).toBe(true); + }); + + // ---- 15. Game over when pieces reach top ---- + test("game over when pieces reach top", async ({ page }) => { + // Stack pieces in the center until the game ends. + // Drop as many pieces as possible straight down. + for (let i = 0; i < 50; i++) { + await page.keyboard.press("Space"); + await page.waitForTimeout(200); + } + + await page.waitForTimeout(2000); + + // After stacking to overflow, the game should show some game-over indicator: + // - text saying "game over", "you lose", "try again", "restart", "end" + // - or the game stops updating (static screen) + const text = ((await page.evaluate(() => document.body.innerText)) || "").toLowerCase(); + const gameOverText = + text.includes("game over") || + text.includes("gameover") || + text.includes("you lose") || + text.includes("try again") || + text.includes("restart") || + text.includes("play again") || + text.includes("ended"); + + // Check if the game stopped (screen is static) + const shot1 = await page.screenshot(); + await page.waitForTimeout(2000); + const shot2 = await page.screenshot(); + const isStatic = Buffer.from(shot1).equals(Buffer.from(shot2)); + + expect( + gameOverText || isStatic, + "Expected game-over text or the game to stop after stacking pieces to the top" + ).toBe(true); + }); + + // ---- 16. Game runs for 30 seconds without crashing ---- + test("game runs for 30 seconds without crashing", async ({ page }) => { + const errors: string[] = []; + page.on("pageerror", (err) => errors.push(err.message)); + + // Simulate varied gameplay for 30 seconds + const keys = [ + "ArrowLeft", + "ArrowRight", + "ArrowDown", + "ArrowUp", + "Space", + ]; + const start = Date.now(); + + while (Date.now() - start < 30_000) { + const key = keys[Math.floor(Math.random() * keys.length)]; + await page.keyboard.press(key); + await page.waitForTimeout(150 + Math.random() * 200); + } + + // The page should still be alive (not crashed, not blank) + const text = await page.evaluate(() => document.body.innerText); + expect(text, "Page body should not be empty after 30s of play").toBeTruthy(); + expect( + errors.length, + `Expected no console errors during 30s of play, got: ${errors.join("; ")}` + ).toBe(0); + }); +}); diff --git a/results/analysis/main_effects_code_quality.json b/results/analysis/main_effects_code_quality.json @@ -3,53 +3,53 @@ "values": { "glm-4.5-air": { "mean": 0.6592, - "effect": -0.0173, + "effect": -0.0171, "n": 37 }, "glm-4.7": { "mean": 0.5674, - "effect": -0.109, + "effect": -0.1089, "n": 101 }, "glm-5.1": { - "mean": 0.5655, - "effect": -0.111, - "n": 97 + "mean": 0.5693, + "effect": -0.107, + "n": 101 }, "haiku": { "mean": 0.7176, - "effect": 0.0411, + "effect": 0.0412, "n": 74 }, "opus": { "mean": 0.8647, - "effect": 0.1882, + "effect": 0.1884, "n": 51 }, "sonnet": { "mean": 0.8644, - "effect": 0.188, + "effect": 0.1881, "n": 52 } }, - "spread": 0.2992 + "spread": 0.2973 }, "playwright": { "values": { "available": { "mean": 0.7978, - "effect": 0.1213, + "effect": 0.1215, "n": 163 }, "instructed": { "mean": 0.5214, - "effect": -0.155, + "effect": -0.1549, "n": 7 }, "off": { - "mean": 0.5992, - "effect": -0.0772, - "n": 242 + "mean": 0.6002, + "effect": -0.0761, + "n": 246 } }, "spread": 0.2764 @@ -58,42 +58,42 @@ "values": { "creative_validate": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "delegate": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "iterate": { "mean": 0.6267, - "effect": -0.0498, + "effect": -0.0497, "n": 6 }, "none": { - "mean": 0.6031, - "effect": -0.0734, - "n": 217 + "mean": 0.6042, + "effect": -0.0722, + "n": 221 }, "plan_first": { "mean": 0.6, - "effect": -0.0765, + "effect": -0.0763, "n": 6 }, "review": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "split_work": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "use_subagents": { "mean": 0.8004, - "effect": 0.124, + "effect": 0.1241, "n": 159 } }, @@ -103,28 +103,28 @@ "values": { "canvas": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "dom": { - "mean": 0.63, - "effect": -0.0465, - "n": 5 + "mean": 0.6583, + "effect": -0.018, + "n": 6 }, "none": { "mean": 0.6819, - "effect": 0.0054, + "effect": 0.0056, "n": 392 }, "svg": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "webgl": { - "mean": 0.55, - "effect": -0.1265, - "n": 3 + "mean": 0.5833, + "effect": -0.093, + "n": 6 } }, "spread": 0.1319 @@ -133,255 +133,255 @@ "values": { "best_practices": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "none": { - "mean": 0.6803, + "mean": 0.6801, "effect": 0.0038, - "n": 400 + "n": 404 }, "separation": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 } }, - "spread": 0.1303 + "spread": 0.1301 }, "design_guidance": { "values": { "none": { - "mean": 0.6803, + "mean": 0.6801, "effect": 0.0038, - "n": 400 + "n": 404 }, "specific": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 }, "vague": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 } }, - "spread": 0.1303 + "spread": 0.1301 }, "error_checking": { "values": { "none": { - "mean": 0.6783, - "effect": 0.0019, - "n": 406 + "mean": 0.6782, + "effect": 0.0018, + "n": 410 }, "self_verify": { "mean": 0.55, - "effect": -0.1265, + "effect": -0.1263, "n": 6 } }, - "spread": 0.1283 + "spread": 0.1282 }, "language": { "values": { "javascript": { "mean": 0.7639, - "effect": 0.0874, + "effect": 0.0876, "n": 18 }, "typescript": { - "mean": 0.6682, - "effect": -0.0083, - "n": 377 + "mean": 0.6681, + "effect": -0.0082, + "n": 381 }, "unspecified": { "mean": 0.7676, - "effect": 0.0912, + "effect": 0.0913, "n": 17 } }, - "spread": 0.0994 + "spread": 0.0995 }, "prompt_style": { "values": { "detailed": { "mean": 0.7617, - "effect": 0.0852, + "effect": 0.0853, "n": 30 }, "simple": { - "mean": 0.6698, - "effect": -0.0067, - "n": 382 + "mean": 0.6697, + "effect": -0.0066, + "n": 386 } }, - "spread": 0.0919 + "spread": 0.092 }, "tool_grep": { "values": { "off": { "mean": 0.7468, - "effect": 0.0703, + "effect": 0.0705, "n": 28 }, "on": { - "mean": 0.6713, + "mean": 0.6712, "effect": -0.0051, - "n": 384 + "n": 388 } }, - "spread": 0.0755 + "spread": 0.0756 }, "linter": { "values": { "off": { "mean": 0.7343, - "effect": 0.0578, + "effect": 0.058, "n": 35 }, "on": { - "mean": 0.6711, - "effect": -0.0054, - "n": 377 + "mean": 0.671, + "effect": -0.0053, + "n": 381 } }, - "spread": 0.0632 + "spread": 0.0633 }, "max_budget": { "values": { "high": { "mean": 0.7343, - "effect": 0.0579, + "effect": 0.058, "n": 23 }, "low": { - "mean": 0.673, + "mean": 0.6729, "effect": -0.0034, - "n": 389 + "n": 393 } }, - "spread": 0.0613 + "spread": 0.0614 }, "web_search": { "values": { "off": { "mean": 0.7288, - "effect": 0.0524, + "effect": 0.0525, "n": 34 }, "on": { - "mean": 0.6717, + "mean": 0.6716, "effect": -0.0047, - "n": 378 + "n": 382 } }, - "spread": 0.0571 + "spread": 0.0572 }, "tool_read": { "values": { "off": { "mean": 0.7293, - "effect": 0.0529, + "effect": 0.053, "n": 29 }, "on": { - "mean": 0.6725, + "mean": 0.6724, "effect": -0.004, - "n": 383 + "n": 387 } }, - "spread": 0.0568 + "spread": 0.0569 }, "tool_write": { "values": { "off": { "mean": 0.7282, - "effect": 0.0518, + "effect": 0.0519, "n": 28 }, "on": { - "mean": 0.6727, - "effect": -0.0038, - "n": 384 + "mean": 0.6726, + "effect": -0.0037, + "n": 388 } }, - "spread": 0.0555 + "spread": 0.0556 }, "tool_edit": { "values": { "off": { "mean": 0.7277, - "effect": 0.0513, + "effect": 0.0514, "n": 31 }, "on": { - "mean": 0.6723, - "effect": -0.0042, - "n": 381 + "mean": 0.6722, + "effect": -0.0041, + "n": 385 } }, - "spread": 0.0554 + "spread": 0.0555 }, "context_file": { "values": { "none": { - "mean": 0.6738, + "mean": 0.6737, "effect": -0.0027, - "n": 384 + "n": 388 }, "provided": { "mean": 0.7132, - "effect": 0.0368, + "effect": 0.0369, "n": 28 } }, - "spread": 0.0394 + "spread": 0.0395 }, "tool_glob": { "values": { "off": { "mean": 0.713, - "effect": 0.0365, + "effect": 0.0366, "n": 27 }, "on": { - "mean": 0.6739, - "effect": -0.0026, - "n": 385 + "mean": 0.6738, + "effect": -0.0025, + "n": 389 } }, - "spread": 0.0391 + "spread": 0.0392 }, "human_language": { "values": { "en": { - "mean": 0.6749, + "mean": 0.6748, "effect": -0.0015, - "n": 385 + "n": 389 }, "es": { "mean": 0.6981, - "effect": 0.0217, + "effect": 0.0218, "n": 27 } }, - "spread": 0.0232 + "spread": 0.0233 }, "effort": { "values": { "high": { - "mean": 0.6763, + "mean": 0.6762, "effect": -0.0002, - "n": 394 + "n": 398 }, "max": { "mean": 0.68, - "effect": 0.0035, + "effect": 0.0037, "n": 18 } }, - "spread": 0.0037 + "spread": 0.0038 } } \ No newline at end of file diff --git a/results/analysis/main_effects_cost.json b/results/analysis/main_effects_cost.json @@ -3,77 +3,77 @@ "values": { "glm-4.5-air": { "mean": 0.2575, - "effect": -0.0955, + "effect": -0.0963, "n": 37 }, "glm-4.7": { "mean": 0.1132, - "effect": -0.2398, + "effect": -0.2406, "n": 101 }, "glm-5.1": { - "mean": 0.0234, - "effect": -0.3295, - "n": 97 + "mean": 0.0335, + "effect": -0.3203, + "n": 99 }, "haiku": { "mean": 0.266, - "effect": -0.087, + "effect": -0.0878, "n": 74 }, "opus": { "mean": 0.6385, - "effect": 0.2855, + "effect": 0.2847, "n": 51 }, "sonnet": { "mean": 1.3451, - "effect": 0.9922, + "effect": 0.9914, "n": 52 } }, - "spread": 1.3217 + "spread": 1.3116 }, "strategy": { "values": { "creative_validate": { "mean": 0.8985, - "effect": 0.5456, + "effect": 0.5447, "n": 6 }, "delegate": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 }, "iterate": { "mean": 0.4896, - "effect": 0.1366, + "effect": 0.1358, "n": 6 }, "none": { - "mean": 0.1215, - "effect": -0.2315, - "n": 217 + "mean": 0.1251, + "effect": -0.2286, + "n": 219 }, "plan_first": { "mean": 0.2719, - "effect": -0.081, + "effect": -0.0818, "n": 6 }, "review": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 }, "split_work": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 }, "use_subagents": { "mean": 0.6861, - "effect": 0.3332, + "effect": 0.3324, "n": 159 } }, @@ -83,18 +83,18 @@ "values": { "available": { "mean": 0.6817, - "effect": 0.3288, + "effect": 0.328, "n": 163 }, "instructed": { "mean": 0.0038, - "effect": -0.3491, + "effect": -0.3499, "n": 7 }, "off": { - "mean": 0.1416, - "effect": -0.2113, - "n": 242 + "mean": 0.1447, + "effect": -0.209, + "n": 244 } }, "spread": 0.6779 @@ -103,28 +103,28 @@ "values": { "canvas": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 }, "dom": { - "mean": 0.1059, - "effect": -0.247, - "n": 5 + "mean": 0.1786, + "effect": -0.1752, + "n": 6 }, "none": { "mean": 0.3693, - "effect": 0.0163, + "effect": 0.0155, "n": 392 }, "svg": { "mean": 0.0208, - "effect": -0.3322, + "effect": -0.333, "n": 6 }, "webgl": { - "mean": 0.0, - "effect": -0.353, - "n": 3 + "mean": 0.1249, + "effect": -0.2289, + "n": 4 } }, "spread": 0.3693 @@ -133,255 +133,255 @@ "values": { "best_practices": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 }, "none": { - "mean": 0.3635, + "mean": 0.3643, "effect": 0.0106, - "n": 400 + "n": 402 }, "separation": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 } }, - "spread": 0.3635 + "spread": 0.3643 }, "design_guidance": { "values": { "none": { - "mean": 0.3635, + "mean": 0.3643, "effect": 0.0106, - "n": 400 + "n": 402 }, "specific": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 }, "vague": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 } }, - "spread": 0.3635 + "spread": 0.3643 }, "error_checking": { "values": { "none": { - "mean": 0.3582, + "mean": 0.359, "effect": 0.0052, - "n": 406 + "n": 408 }, "self_verify": { "mean": 0.0, - "effect": -0.353, + "effect": -0.3538, "n": 6 } }, - "spread": 0.3582 + "spread": 0.359 }, "effort": { "values": { "high": { - "mean": 0.3416, - "effect": -0.0113, - "n": 394 + "mean": 0.3425, + "effect": -0.0112, + "n": 396 }, "max": { "mean": 0.6006, - "effect": 0.2477, + "effect": 0.2469, "n": 18 } }, - "spread": 0.259 + "spread": 0.2581 }, "web_search": { "values": { "off": { "mean": 0.4189, - "effect": 0.066, + "effect": 0.0652, "n": 34 }, "on": { - "mean": 0.347, - "effect": -0.0059, - "n": 378 + "mean": 0.3479, + "effect": -0.0058, + "n": 380 } }, - "spread": 0.0719 + "spread": 0.071 }, "prompt_style": { "values": { "detailed": { "mean": 0.4041, - "effect": 0.0511, + "effect": 0.0503, "n": 30 }, "simple": { - "mean": 0.3489, - "effect": -0.004, - "n": 382 + "mean": 0.3498, + "effect": -0.0039, + "n": 384 } }, - "spread": 0.0552 + "spread": 0.0543 }, "context_file": { "values": { "none": { - "mean": 0.3494, - "effect": -0.0036, - "n": 384 + "mean": 0.3503, + "effect": -0.0035, + "n": 386 }, "provided": { "mean": 0.4019, - "effect": 0.049, + "effect": 0.0481, "n": 28 } }, - "spread": 0.0525 + "spread": 0.0516 + }, + "tool_edit": { + "values": { + "off": { + "mean": 0.3174, + "effect": -0.0363, + "n": 31 + }, + "on": { + "mean": 0.3567, + "effect": 0.0029, + "n": 383 + } + }, + "spread": 0.0393 }, "language": { "values": { "javascript": { "mean": 0.3246, - "effect": -0.0284, + "effect": -0.0292, "n": 18 }, "typescript": { - "mean": 0.3538, + "mean": 0.3547, "effect": 0.0009, - "n": 377 + "n": 379 }, "unspecified": { "mean": 0.3637, - "effect": 0.0107, + "effect": 0.0099, "n": 17 } }, "spread": 0.0391 }, - "tool_edit": { - "values": { - "off": { - "mean": 0.3174, - "effect": -0.0355, - "n": 31 - }, - "on": { - "mean": 0.3558, - "effect": 0.0029, - "n": 381 - } - }, - "spread": 0.0384 - }, "human_language": { "values": { "en": { - "mean": 0.3505, - "effect": -0.0025, - "n": 385 + "mean": 0.3514, + "effect": -0.0024, + "n": 387 }, "es": { "mean": 0.3879, - "effect": 0.035, + "effect": 0.0342, "n": 27 } }, - "spread": 0.0374 + "spread": 0.0365 }, "max_budget": { "values": { "high": { "mean": 0.3852, - "effect": 0.0323, + "effect": 0.0315, "n": 23 }, "low": { - "mean": 0.351, + "mean": 0.3519, "effect": -0.0019, - "n": 389 + "n": 391 } }, - "spread": 0.0342 + "spread": 0.0333 }, "tool_read": { "values": { "off": { "mean": 0.375, - "effect": 0.0221, + "effect": 0.0213, "n": 29 }, "on": { - "mean": 0.3513, - "effect": -0.0017, - "n": 383 + "mean": 0.3522, + "effect": -0.0016, + "n": 385 } }, - "spread": 0.0237 + "spread": 0.0228 }, "tool_write": { "values": { "off": { "mean": 0.3718, - "effect": 0.0188, + "effect": 0.018, "n": 28 }, "on": { - "mean": 0.3516, - "effect": -0.0014, - "n": 384 + "mean": 0.3525, + "effect": -0.0013, + "n": 386 } }, - "spread": 0.0202 + "spread": 0.0193 }, "tool_grep": { "values": { "off": { "mean": 0.3685, - "effect": 0.0156, + "effect": 0.0148, "n": 28 }, "on": { - "mean": 0.3518, + "mean": 0.3527, "effect": -0.0011, - "n": 384 + "n": 386 } }, - "spread": 0.0167 + "spread": 0.0158 }, "linter": { "values": { "off": { "mean": 0.3596, - "effect": 0.0066, + "effect": 0.0058, "n": 35 }, "on": { - "mean": 0.3523, - "effect": -0.0006, - "n": 377 + "mean": 0.3532, + "effect": -0.0005, + "n": 379 } }, - "spread": 0.0073 + "spread": 0.0064 }, "tool_glob": { "values": { "off": { "mean": 0.3575, - "effect": 0.0046, + "effect": 0.0038, "n": 27 }, "on": { - "mean": 0.3526, + "mean": 0.3535, "effect": -0.0003, - "n": 385 + "n": 387 } }, - "spread": 0.0049 + "spread": 0.004 } } \ No newline at end of file diff --git a/results/analysis/main_effects_gameplay.json b/results/analysis/main_effects_gameplay.json @@ -3,32 +3,32 @@ "values": { "glm-4.5-air": { "mean": 0.313, - "effect": 0.0242, + "effect": 0.0258, "n": 37 }, "glm-4.7": { "mean": 0.0464, - "effect": -0.2424, + "effect": -0.2408, "n": 101 }, "glm-5.1": { - "mean": 0.0484, - "effect": -0.2404, - "n": 97 + "mean": 0.0514, + "effect": -0.2358, + "n": 101 }, "haiku": { "mean": 0.5473, - "effect": 0.2585, + "effect": 0.2601, "n": 74 }, "opus": { "mean": 0.661, - "effect": 0.3722, + "effect": 0.3738, "n": 51 }, "sonnet": { "mean": 0.4579, - "effect": 0.1691, + "effect": 0.1707, "n": 52 } }, @@ -38,18 +38,18 @@ "values": { "available": { "mean": 0.5326, - "effect": 0.2439, + "effect": 0.2454, "n": 163 }, "instructed": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 7 }, "off": { - "mean": 0.1329, - "effect": -0.1559, - "n": 242 + "mean": 0.1328, + "effect": -0.1544, + "n": 246 } }, "spread": 0.5326 @@ -58,42 +58,42 @@ "values": { "creative_validate": { "mean": 0.0417, - "effect": -0.2471, + "effect": -0.2455, "n": 6 }, "delegate": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 }, "iterate": { "mean": 0.1883, - "effect": -0.1005, + "effect": -0.0989, "n": 6 }, "none": { - "mean": 0.1531, - "effect": -0.1357, - "n": 217 + "mean": 0.1526, + "effect": -0.1346, + "n": 221 }, "plan_first": { "mean": 0.125, - "effect": -0.1638, + "effect": -0.1622, "n": 6 }, "review": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 }, "split_work": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 }, "use_subagents": { "mean": 0.5259, - "effect": 0.2371, + "effect": 0.2387, "n": 159 } }, @@ -103,285 +103,285 @@ "values": { "canvas": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 }, "dom": { - "mean": 0.4, - "effect": 0.1112, - "n": 5 + "mean": 0.375, + "effect": 0.0878, + "n": 6 }, "none": { "mean": 0.2984, - "effect": 0.0096, + "effect": 0.0112, "n": 392 }, "svg": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 }, "webgl": { - "mean": 0.0, - "effect": -0.2888, - "n": 3 + "mean": 0.0417, + "effect": -0.2455, + "n": 6 + } + }, + "spread": 0.375 + }, + "max_budget": { + "values": { + "high": { + "mean": 0.5691, + "effect": 0.2819, + "n": 23 + }, + "low": { + "mean": 0.2707, + "effect": -0.0165, + "n": 393 } }, - "spread": 0.4 + "spread": 0.2984 }, "architecture": { "values": { "best_practices": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 }, "none": { - "mean": 0.2974, - "effect": 0.0087, - "n": 400 + "mean": 0.2957, + "effect": 0.0085, + "n": 404 }, "separation": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 } }, - "spread": 0.2974 + "spread": 0.2957 }, "design_guidance": { "values": { "none": { - "mean": 0.2974, - "effect": 0.0087, - "n": 400 + "mean": 0.2957, + "effect": 0.0085, + "n": 404 }, "specific": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 }, "vague": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 } }, - "spread": 0.2974 - }, - "max_budget": { - "values": { - "high": { - "mean": 0.5691, - "effect": 0.2803, - "n": 23 - }, - "low": { - "mean": 0.2722, - "effect": -0.0166, - "n": 389 - } - }, - "spread": 0.2969 + "spread": 0.2957 }, "error_checking": { "values": { "none": { - "mean": 0.2931, - "effect": 0.0043, - "n": 406 + "mean": 0.2914, + "effect": 0.0042, + "n": 410 }, "self_verify": { "mean": 0.0, - "effect": -0.2888, + "effect": -0.2872, "n": 6 } }, - "spread": 0.2931 + "spread": 0.2914 }, "tool_glob": { "values": { "off": { "mean": 0.5585, - "effect": 0.2697, + "effect": 0.2713, "n": 27 }, "on": { - "mean": 0.2699, - "effect": -0.0189, - "n": 385 + "mean": 0.2684, + "effect": -0.0188, + "n": 389 } }, - "spread": 0.2886 + "spread": 0.2901 }, "context_file": { "values": { "none": { - "mean": 0.2694, - "effect": -0.0194, - "n": 384 + "mean": 0.2679, + "effect": -0.0193, + "n": 388 }, "provided": { "mean": 0.555, - "effect": 0.2662, + "effect": 0.2678, "n": 28 } }, - "spread": 0.2856 + "spread": 0.2871 }, "tool_grep": { "values": { "off": { "mean": 0.4943, - "effect": 0.2055, + "effect": 0.2071, "n": 28 }, "on": { - "mean": 0.2738, - "effect": -0.015, - "n": 384 + "mean": 0.2723, + "effect": -0.0149, + "n": 388 } }, - "spread": 0.2205 + "spread": 0.222 }, "tool_write": { "values": { "off": { "mean": 0.4807, - "effect": 0.1919, + "effect": 0.1935, "n": 28 }, "on": { - "mean": 0.2748, + "mean": 0.2732, "effect": -0.014, - "n": 384 + "n": 388 } }, - "spread": 0.2059 + "spread": 0.2075 }, "tool_edit": { "values": { "off": { "mean": 0.471, - "effect": 0.1822, + "effect": 0.1838, "n": 31 }, "on": { - "mean": 0.274, + "mean": 0.2724, "effect": -0.0148, - "n": 381 + "n": 385 } }, - "spread": 0.197 + "spread": 0.1986 }, "language": { "values": { "javascript": { "mean": 0.2367, - "effect": -0.0521, + "effect": -0.0505, "n": 18 }, "typescript": { - "mean": 0.2997, - "effect": 0.0109, - "n": 377 + "mean": 0.2978, + "effect": 0.0106, + "n": 381 }, "unspecified": { "mean": 0.1029, - "effect": -0.1858, + "effect": -0.1843, "n": 17 } }, - "spread": 0.1968 + "spread": 0.1949 }, "web_search": { "values": { "off": { "mean": 0.4121, - "effect": 0.1233, + "effect": 0.1248, "n": 34 }, "on": { - "mean": 0.2777, + "mean": 0.2761, "effect": -0.0111, - "n": 378 + "n": 382 } }, - "spread": 0.1344 + "spread": 0.136 }, "prompt_style": { "values": { "detailed": { "mean": 0.3693, - "effect": 0.0805, + "effect": 0.0821, "n": 30 }, "simple": { - "mean": 0.2825, - "effect": -0.0063, - "n": 382 + "mean": 0.2808, + "effect": -0.0064, + "n": 386 } }, - "spread": 0.0868 + "spread": 0.0885 }, "linter": { "values": { "off": { "mean": 0.3666, - "effect": 0.0778, + "effect": 0.0794, "n": 35 }, "on": { - "mean": 0.2816, - "effect": -0.0072, - "n": 377 + "mean": 0.2799, + "effect": -0.0073, + "n": 381 } }, - "spread": 0.085 + "spread": 0.0867 }, "human_language": { "values": { "en": { - "mean": 0.2842, - "effect": -0.0046, - "n": 385 + "mean": 0.2825, + "effect": -0.0047, + "n": 389 }, "es": { "mean": 0.3544, - "effect": 0.0657, + "effect": 0.0672, "n": 27 } }, - "spread": 0.0702 + "spread": 0.0719 }, "tool_read": { "values": { "off": { "mean": 0.3393, - "effect": 0.0505, + "effect": 0.0521, "n": 29 }, "on": { - "mean": 0.285, - "effect": -0.0038, - "n": 383 + "mean": 0.2833, + "effect": -0.0039, + "n": 387 } }, - "spread": 0.0543 + "spread": 0.056 }, "effort": { "values": { "high": { - "mean": 0.289, - "effect": 0.0002, - "n": 394 + "mean": 0.2873, + "effect": 0.0001, + "n": 398 }, "max": { "mean": 0.285, - "effect": -0.0038, + "effect": -0.0022, "n": 18 } }, - "spread": 0.004 + "spread": 0.0023 } } \ No newline at end of file diff --git a/results/analysis/main_effects_score.json b/results/analysis/main_effects_score.json @@ -3,32 +3,32 @@ "values": { "glm-4.5-air": { "mean": 0.4032, - "effect": 0.1001, + "effect": 0.1024, "n": 37 }, "glm-4.7": { "mean": 0.0356, - "effect": -0.2676, + "effect": -0.2652, "n": 101 }, "glm-5.1": { - "mean": 0.0403, - "effect": -0.2629, - "n": 97 + "mean": 0.0411, + "effect": -0.2597, + "n": 101 }, "haiku": { "mean": 0.5502, - "effect": 0.247, + "effect": 0.2494, "n": 74 }, "opus": { "mean": 0.6692, - "effect": 0.3661, + "effect": 0.3684, "n": 51 }, "sonnet": { "mean": 0.5314, - "effect": 0.2283, + "effect": 0.2306, "n": 52 } }, @@ -38,42 +38,42 @@ "values": { "creative_validate": { "mean": 0.0208, - "effect": -0.2823, + "effect": -0.28, "n": 6 }, "delegate": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 }, "iterate": { "mean": 0.0942, - "effect": -0.209, + "effect": -0.2067, "n": 6 }, "none": { - "mean": 0.1623, - "effect": -0.1409, - "n": 217 + "mean": 0.1605, + "effect": -0.1403, + "n": 221 }, "plan_first": { "mean": 0.0625, - "effect": -0.2407, + "effect": -0.2383, "n": 6 }, "review": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 }, "split_work": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 }, "use_subagents": { "mean": 0.5573, - "effect": 0.2542, + "effect": 0.2565, "n": 159 } }, @@ -83,18 +83,18 @@ "values": { "available": { "mean": 0.5559, - "effect": 0.2527, + "effect": 0.255, "n": 163 }, "instructed": { "mean": 0.0586, - "effect": -0.2446, + "effect": -0.2423, "n": 7 }, "off": { - "mean": 0.14, - "effect": -0.1631, - "n": 242 + "mean": 0.1388, + "effect": -0.1621, + "n": 246 } }, "spread": 0.4973 @@ -103,28 +103,28 @@ "values": { "canvas": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 }, "dom": { - "mean": 0.2, - "effect": -0.1032, - "n": 5 + "mean": 0.1875, + "effect": -0.1133, + "n": 6 }, "none": { "mean": 0.3161, - "effect": 0.0129, + "effect": 0.0152, "n": 392 }, "svg": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 }, "webgl": { - "mean": 0.0, - "effect": -0.3032, - "n": 3 + "mean": 0.0208, + "effect": -0.28, + "n": 6 } }, "spread": 0.3161 @@ -133,255 +133,255 @@ "values": { "best_practices": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 }, "none": { - "mean": 0.3123, - "effect": 0.0091, - "n": 400 + "mean": 0.3098, + "effect": 0.0089, + "n": 404 }, "separation": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 } }, - "spread": 0.3123 + "spread": 0.3098 }, "design_guidance": { "values": { "none": { - "mean": 0.3123, - "effect": 0.0091, - "n": 400 + "mean": 0.3098, + "effect": 0.0089, + "n": 404 }, "specific": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 }, "vague": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 } }, - "spread": 0.3123 + "spread": 0.3098 }, "error_checking": { "values": { "none": { - "mean": 0.3076, - "effect": 0.0045, - "n": 406 + "mean": 0.3052, + "effect": 0.0044, + "n": 410 }, "self_verify": { "mean": 0.0, - "effect": -0.3032, + "effect": -0.3008, "n": 6 } }, - "spread": 0.3076 + "spread": 0.3052 }, "context_file": { "values": { "none": { - "mean": 0.2894, - "effect": -0.0137, - "n": 384 + "mean": 0.2871, + "effect": -0.0138, + "n": 388 }, "provided": { "mean": 0.4914, - "effect": 0.1883, + "effect": 0.1906, "n": 28 } }, - "spread": 0.202 + "spread": 0.2043 }, "max_budget": { "values": { "high": { "mean": 0.492, - "effect": 0.1888, + "effect": 0.1911, "n": 23 }, "low": { - "mean": 0.292, + "mean": 0.2897, "effect": -0.0112, - "n": 389 + "n": 393 } }, - "spread": 0.2 + "spread": 0.2023 }, "tool_grep": { "values": { "off": { "mean": 0.4868, - "effect": 0.1836, + "effect": 0.1859, "n": 28 }, "on": { - "mean": 0.2898, + "mean": 0.2874, "effect": -0.0134, - "n": 384 + "n": 388 } }, - "spread": 0.197 + "spread": 0.1994 }, "tool_glob": { "values": { "off": { "mean": 0.4728, - "effect": 0.1696, + "effect": 0.1719, "n": 27 }, "on": { - "mean": 0.2913, + "mean": 0.2889, "effect": -0.0119, - "n": 385 + "n": 389 } }, - "spread": 0.1815 + "spread": 0.1839 }, "tool_write": { "values": { "off": { "mean": 0.4539, - "effect": 0.1508, + "effect": 0.1531, "n": 28 }, "on": { - "mean": 0.2922, + "mean": 0.2898, "effect": -0.011, - "n": 384 + "n": 388 } }, - "spread": 0.1617 + "spread": 0.1641 }, "tool_edit": { "values": { "off": { "mean": 0.4523, - "effect": 0.1491, + "effect": 0.1514, "n": 31 }, "on": { - "mean": 0.291, - "effect": -0.0121, - "n": 381 + "mean": 0.2886, + "effect": -0.0122, + "n": 385 } }, - "spread": 0.1613 + "spread": 0.1637 }, "web_search": { "values": { "off": { "mean": 0.4437, - "effect": 0.1405, + "effect": 0.1428, "n": 34 }, "on": { - "mean": 0.2905, - "effect": -0.0126, - "n": 378 + "mean": 0.2881, + "effect": -0.0127, + "n": 382 } }, - "spread": 0.1532 + "spread": 0.1556 }, "linter": { "values": { "off": { "mean": 0.4416, - "effect": 0.1384, + "effect": 0.1407, "n": 35 }, "on": { - "mean": 0.2903, + "mean": 0.2879, "effect": -0.0129, - "n": 377 + "n": 381 } }, - "spread": 0.1513 + "spread": 0.1537 }, "prompt_style": { "values": { "detailed": { "mean": 0.4348, - "effect": 0.1317, + "effect": 0.134, "n": 30 }, "simple": { - "mean": 0.2928, - "effect": -0.0103, - "n": 382 + "mean": 0.2904, + "effect": -0.0104, + "n": 386 } }, - "spread": 0.142 + "spread": 0.1444 }, "tool_read": { "values": { "off": { "mean": 0.4043, - "effect": 0.1012, + "effect": 0.1035, "n": 29 }, "on": { - "mean": 0.2955, - "effect": -0.0077, - "n": 383 + "mean": 0.2931, + "effect": -0.0078, + "n": 387 } }, - "spread": 0.1088 + "spread": 0.1112 }, "human_language": { "values": { "en": { - "mean": 0.2971, - "effect": -0.0061, - "n": 385 + "mean": 0.2947, + "effect": -0.0062, + "n": 389 }, "es": { "mean": 0.39, - "effect": 0.0868, + "effect": 0.0892, "n": 27 } }, - "spread": 0.0929 + "spread": 0.0953 }, "language": { "values": { "javascript": { "mean": 0.3767, - "effect": 0.0735, + "effect": 0.0758, "n": 18 }, "typescript": { - "mean": 0.2988, - "effect": -0.0044, - "n": 377 + "mean": 0.2963, + "effect": -0.0045, + "n": 381 }, "unspecified": { "mean": 0.3224, - "effect": 0.0192, + "effect": 0.0215, "n": 17 } }, - "spread": 0.0779 + "spread": 0.0804 }, "effort": { "values": { "high": { - "mean": 0.3027, - "effect": -0.0004, - "n": 394 + "mean": 0.3003, + "effect": -0.0005, + "n": 398 }, "max": { "mean": 0.3125, - "effect": 0.0093, + "effect": 0.0117, "n": 18 } }, - "spread": 0.0098 + "spread": 0.0122 } } \ No newline at end of file diff --git a/results/analysis/main_effects_turns.json b/results/analysis/main_effects_turns.json @@ -3,42 +3,42 @@ "values": { "creative_validate": { "mean": 35.1667, - "effect": 22.8511, + "effect": 22.7343, "n": 6 }, "delegate": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 }, "iterate": { "mean": 1.1667, - "effect": -11.1489, + "effect": -11.2657, "n": 6 }, "none": { - "mean": 6.7051, - "effect": -5.6105, - "n": 217 + "mean": 6.9772, + "effect": -5.4552, + "n": 219 }, "plan_first": { "mean": 1.3333, - "effect": -10.9822, + "effect": -11.099, "n": 6 }, "review": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 }, "split_work": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 }, "use_subagents": { "mean": 21.2264, - "effect": 8.9109, + "effect": 8.794, "n": 159 } }, @@ -48,53 +48,53 @@ "values": { "glm-4.5-air": { "mean": 13.2703, - "effect": 0.9547, + "effect": 0.8379, "n": 37 }, "glm-4.7": { "mean": 3.7921, - "effect": -8.5235, + "effect": -8.6403, "n": 101 }, "glm-5.1": { - "mean": 2.4227, - "effect": -9.8929, - "n": 97 + "mean": 3.1111, + "effect": -9.3213, + "n": 99 }, "haiku": { "mean": 26.2838, - "effect": 13.9682, + "effect": 13.8514, "n": 74 }, "opus": { "mean": 19.2549, - "effect": 6.9394, + "effect": 6.8225, "n": 51 }, "sonnet": { "mean": 19.9615, - "effect": 7.646, + "effect": 7.5292, "n": 52 } }, - "spread": 23.8611 + "spread": 23.1727 }, "playwright": { "values": { "available": { "mean": 21.1779, - "effect": 8.8624, + "effect": 8.7455, "n": 163 }, "instructed": { "mean": 1.5714, - "effect": -10.7441, + "effect": -10.8609, "n": 7 }, "off": { - "mean": 6.657, - "effect": -5.6585, - "n": 242 + "mean": 6.9016, + "effect": -5.5307, + "n": 244 } }, "spread": 19.6065 @@ -103,285 +103,285 @@ "values": { "canvas": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 }, "dom": { - "mean": 10.0, - "effect": -2.3155, - "n": 5 + "mean": 14.0, + "effect": 1.5676, + "n": 6 }, "none": { "mean": 12.7781, - "effect": 0.4625, + "effect": 0.3457, "n": 392 }, "svg": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 }, "webgl": { - "mean": 1.0, - "effect": -11.3155, - "n": 3 + "mean": 10.5, + "effect": -1.9324, + "n": 4 } }, - "spread": 11.7781 + "spread": 13.0 }, "architecture": { "values": { "best_practices": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 }, "none": { - "mean": 12.655, - "effect": 0.3395, - "n": 400 + "mean": 12.7736, + "effect": 0.3413, + "n": 402 }, "separation": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 } }, - "spread": 11.655 + "spread": 11.7736 }, "design_guidance": { "values": { "none": { - "mean": 12.655, - "effect": 0.3395, - "n": 400 + "mean": 12.7736, + "effect": 0.3413, + "n": 402 }, "specific": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 }, "vague": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 } }, - "spread": 11.655 + "spread": 11.7736 }, "error_checking": { "values": { "none": { - "mean": 12.4828, - "effect": 0.1672, - "n": 406 + "mean": 12.6005, + "effect": 0.1681, + "n": 408 }, "self_verify": { "mean": 1.0, - "effect": -11.3155, + "effect": -11.4324, "n": 6 } }, - "spread": 11.4828 + "spread": 11.6005 }, "tool_write": { "values": { "off": { "mean": 20.5, - "effect": 8.1845, + "effect": 8.0676, "n": 28 }, "on": { - "mean": 11.7188, - "effect": -0.5968, - "n": 384 + "mean": 11.8472, + "effect": -0.5852, + "n": 386 } }, - "spread": 8.7812 + "spread": 8.6528 }, "web_search": { "values": { "off": { "mean": 20.3529, - "effect": 8.0374, + "effect": 7.9206, "n": 34 }, "on": { - "mean": 11.5926, - "effect": -0.7229, - "n": 378 + "mean": 11.7237, + "effect": -0.7087, + "n": 380 } }, - "spread": 8.7603 + "spread": 8.6292 }, "context_file": { "values": { "none": { - "mean": 11.7396, - "effect": -0.576, - "n": 384 + "mean": 11.8679, + "effect": -0.5645, + "n": 386 }, "provided": { "mean": 20.2143, - "effect": 7.8988, + "effect": 7.7819, "n": 28 } }, - "spread": 8.4747 + "spread": 8.3464 }, "human_language": { "values": { "en": { - "mean": 11.813, - "effect": -0.5025, - "n": 385 + "mean": 11.9406, + "effect": -0.4918, + "n": 387 }, "es": { "mean": 19.4815, - "effect": 7.1659, + "effect": 7.0491, "n": 27 } }, - "spread": 7.6685 + "spread": 7.5409 }, "max_budget": { "values": { "high": { "mean": 19.5217, - "effect": 7.2062, + "effect": 7.0894, "n": 23 }, "low": { - "mean": 11.8895, - "effect": -0.4261, - "n": 389 + "mean": 12.0153, + "effect": -0.417, + "n": 391 + } + }, + "spread": 7.5064 + }, + "language": { + "values": { + "javascript": { + "mean": 5.7778, + "effect": -6.6546, + "n": 18 + }, + "typescript": { + "mean": 13.0264, + "effect": 0.594, + "n": 379 + }, + "unspecified": { + "mean": 6.2353, + "effect": -6.1971, + "n": 17 } }, - "spread": 7.6322 + "spread": 7.2486 }, "effort": { "values": { "high": { - "mean": 11.9975, - "effect": -0.3181, - "n": 394 + "mean": 12.1212, + "effect": -0.3112, + "n": 396 }, "max": { "mean": 19.2778, - "effect": 6.9622, + "effect": 6.8454, "n": 18 } }, - "spread": 7.2803 + "spread": 7.1566 }, "tool_read": { "values": { "off": { "mean": 18.9655, - "effect": 6.65, + "effect": 6.5332, "n": 29 }, "on": { - "mean": 11.812, - "effect": -0.5035, - "n": 383 - } - }, - "spread": 7.1535 - }, - "language": { - "values": { - "javascript": { - "mean": 5.7778, - "effect": -6.5378, - "n": 18 - }, - "typescript": { - "mean": 12.9019, - "effect": 0.5863, - "n": 377 - }, - "unspecified": { - "mean": 6.2353, - "effect": -6.0802, - "n": 17 + "mean": 11.9403, + "effect": -0.4921, + "n": 385 } }, - "spread": 7.1241 + "spread": 7.0252 }, "tool_glob": { "values": { "off": { "mean": 18.5926, - "effect": 6.2771, + "effect": 6.1602, "n": 27 }, "on": { - "mean": 11.8753, - "effect": -0.4402, - "n": 385 + "mean": 12.0026, + "effect": -0.4298, + "n": 387 } }, - "spread": 6.7173 + "spread": 6.59 }, "tool_edit": { "values": { "off": { "mean": 18.2903, - "effect": 5.9748, + "effect": 5.858, "n": 31 }, "on": { - "mean": 11.8294, - "effect": -0.4861, - "n": 381 + "mean": 11.9582, + "effect": -0.4741, + "n": 383 } }, - "spread": 6.4609 + "spread": 6.3321 }, "tool_grep": { "values": { "off": { "mean": 18.2143, - "effect": 5.8988, + "effect": 5.7819, "n": 28 }, "on": { - "mean": 11.8854, - "effect": -0.4301, - "n": 384 + "mean": 12.013, + "effect": -0.4194, + "n": 386 } }, - "spread": 6.3289 + "spread": 6.2013 }, "linter": { "values": { "off": { "mean": 17.6, - "effect": 5.2845, + "effect": 5.1676, "n": 35 }, "on": { - "mean": 11.8249, - "effect": -0.4906, - "n": 377 + "mean": 11.9551, + "effect": -0.4772, + "n": 379 } }, - "spread": 5.7751 + "spread": 5.6449 }, "prompt_style": { "values": { "detailed": { "mean": 14.9, - "effect": 2.5845, + "effect": 2.4676, "n": 30 }, "simple": { - "mean": 12.1126, - "effect": -0.203, - "n": 382 + "mean": 12.2396, + "effect": -0.1928, + "n": 384 } }, - "spread": 2.7874 + "spread": 2.6604 } } \ No newline at end of file diff --git a/results/analysis/main_effects_wall_time.json b/results/analysis/main_effects_wall_time.json @@ -3,77 +3,107 @@ "values": { "glm-4.5-air": { "mean": 386.3784, - "effect": 68.9463, + "effect": 62.2942, "n": 37 }, "glm-4.7": { "mean": 251.4455, - "effect": -65.9865, + "effect": -72.6386, "n": 101 }, "glm-5.1": { - "mean": 220.3299, - "effect": -97.1021, - "n": 97 + "mean": 251.5743, + "effect": -72.5099, + "n": 101 }, "haiku": { "mean": 219.973, - "effect": -97.4591, + "effect": -104.1112, "n": 74 }, "opus": { "mean": 224.3529, - "effect": -93.0791, + "effect": -99.7312, "n": 51 }, "sonnet": { "mean": 807.6538, - "effect": 490.2218, + "effect": 483.5697, "n": 52 } }, "spread": 587.6808 }, + "renderer": { + "values": { + "canvas": { + "mean": 198.1667, + "effect": -125.9175, + "n": 6 + }, + "dom": { + "mean": 381.8333, + "effect": 57.7492, + "n": 6 + }, + "none": { + "mean": 321.9974, + "effect": -2.0867, + "n": 392 + }, + "svg": { + "mean": 210.1667, + "effect": -113.9175, + "n": 6 + }, + "webgl": { + "mean": 642.5, + "effect": 318.4159, + "n": 6 + } + }, + "spread": 444.3333 + }, "strategy": { "values": { "creative_validate": { "mean": 470.0, - "effect": 152.568, + "effect": 145.9159, "n": 6 }, "delegate": { "mean": 201.3333, - "effect": -116.0987, + "effect": -122.7508, "n": 6 }, "iterate": { "mean": 415.8333, - "effect": 98.4013, + "effect": 91.7492, "n": 6 }, "none": { - "mean": 254.7419, - "effect": -62.6901, - "n": 217 + "mean": 268.3982, + "effect": -55.6859, + "n": 221 }, "plan_first": { "mean": 561.6667, - "effect": 244.2346, + "effect": 237.5825, "n": 6 }, "review": { "mean": 198.5, - "effect": -118.932, + "effect": -125.5841, "n": 6 }, "split_work": { "mean": 200.5, - "effect": -116.932, + "effect": -123.5841, "n": 6 }, "use_subagents": { "mean": 397.5849, - "effect": 80.1529, + "effect": 73.5008, "n": 159 } }, @@ -83,18 +113,18 @@ "values": { "available": { "mean": 394.1288, - "effect": 76.6968, + "effect": 70.0447, "n": 163 }, "instructed": { "mean": 209.1429, - "effect": -108.2892, + "effect": -114.9413, "n": 7 }, "off": { - "mean": 268.905, - "effect": -48.5271, - "n": 242 + "mean": 280.9431, + "effect": -43.141, + "n": 246 } }, "spread": 184.9859 @@ -102,286 +132,256 @@ "effort": { "values": { "high": { - "mean": 310.4848, - "effect": -6.9473, - "n": 394 + "mean": 317.5075, + "effect": -6.5766, + "n": 398 }, "max": { "mean": 469.5, - "effect": 152.068, + "effect": 145.4159, "n": 18 } }, - "spread": 159.0152 + "spread": 151.9925 }, "design_guidance": { "values": { "none": { - "mean": 321.05, - "effect": 3.618, - "n": 400 + "mean": 327.8639, + "effect": 3.7797, + "n": 404 }, "specific": { "mean": 199.0, - "effect": -118.432, + "effect": -125.0841, "n": 6 }, "vague": { "mean": 194.6667, - "effect": -122.7654, + "effect": -129.4175, "n": 6 } }, - "spread": 126.3833 - }, - "renderer": { - "values": { - "canvas": { - "mean": 198.1667, - "effect": -119.2654, - "n": 6 - }, - "dom": { - "mean": 303.0, - "effect": -14.432, - "n": 5 - }, - "none": { - "mean": 321.9974, - "effect": 4.5654, - "n": 392 - }, - "svg": { - "mean": 210.1667, - "effect": -107.2654, - "n": 6 - }, - "webgl": { - "mean": 198.0, - "effect": -119.432, - "n": 3 - } - }, - "spread": 123.9974 + "spread": 133.1972 }, "architecture": { "values": { "best_practices": { "mean": 202.5, - "effect": -114.932, + "effect": -121.5841, "n": 6 }, "none": { - "mean": 320.9575, - "effect": 3.5255, - "n": 400 + "mean": 327.7723, + "effect": 3.6881, + "n": 404 }, "separation": { "mean": 197.3333, - "effect": -120.0987, + "effect": -126.7508, "n": 6 } }, - "spread": 123.6242 + "spread": 130.439 }, "error_checking": { "values": { "none": { - "mean": 319.17, - "effect": 1.7379, - "n": 406 + "mean": 325.9024, + "effect": 1.8183, + "n": 410 }, "self_verify": { "mean": 199.8333, - "effect": -117.5987, + "effect": -124.2508, "n": 6 } }, - "spread": 119.3367 + "spread": 126.0691 }, "tool_edit": { "values": { "off": { "mean": 275.2581, - "effect": -42.174, + "effect": -48.8261, "n": 31 }, "on": { - "mean": 320.8635, - "effect": 3.4315, - "n": 381 - } - }, - "spread": 45.6054 - }, - "web_search": { - "values": { - "off": { - "mean": 354.0, - "effect": 36.568, - "n": 34 - }, - "on": { - "mean": 314.1429, - "effect": -3.2892, - "n": 378 + "mean": 328.0156, + "effect": 3.9314, + "n": 385 } }, - "spread": 39.8571 + "spread": 52.7575 }, "context_file": { "values": { "none": { - "mean": 320.0599, - "effect": 2.6279, - "n": 384 + "mean": 327.1649, + "effect": 3.0808, + "n": 388 }, "provided": { "mean": 281.3929, - "effect": -36.0392, + "effect": -42.6913, "n": 28 } }, - "spread": 38.667 + "spread": 45.772 }, "max_budget": { "values": { "high": { "mean": 281.7826, - "effect": -35.6494, + "effect": -42.3015, "n": 23 }, "low": { - "mean": 319.5398, - "effect": 2.1078, - "n": 389 + "mean": 326.5598, + "effect": 2.4757, + "n": 393 } }, - "spread": 37.7572 + "spread": 44.7772 }, "tool_write": { "values": { "off": { "mean": 285.3571, - "effect": -32.0749, + "effect": -38.727, "n": 28 }, "on": { - "mean": 319.7708, - "effect": 2.3388, - "n": 384 + "mean": 326.8789, + "effect": 2.7947, + "n": 388 } }, - "spread": 34.4137 + "spread": 41.5218 }, "tool_glob": { "values": { "off": { "mean": 288.7037, - "effect": -28.7283, + "effect": -35.3804, "n": 27 }, "on": { - "mean": 319.4468, - "effect": 2.0147, - "n": 385 + "mean": 326.5398, + "effect": 2.4557, + "n": 389 } }, - "spread": 30.7431 + "spread": 37.8361 }, "tool_grep": { "values": { "off": { "mean": 290.8214, - "effect": -26.6106, + "effect": -33.2627, "n": 28 }, "on": { - "mean": 319.3724, - "effect": 1.9404, - "n": 384 + "mean": 326.4845, + "effect": 2.4004, + "n": 388 } }, - "spread": 28.551 + "spread": 35.6631 }, "language": { "values": { "javascript": { "mean": 291.6667, - "effect": -25.7654, + "effect": -32.4175, "n": 18 }, "typescript": { - "mean": 319.0822, - "effect": 1.6502, - "n": 377 + "mean": 326.3281, + "effect": 2.2439, + "n": 381 }, "unspecified": { "mean": 308.1176, - "effect": -9.3144, + "effect": -15.9665, "n": 17 } }, - "spread": 27.4155 + "spread": 34.6614 + }, + "web_search": { + "values": { + "off": { + "mean": 354.0, + "effect": 29.9159, + "n": 34 + }, + "on": { + "mean": 321.4215, + "effect": -2.6627, + "n": 382 + } + }, + "spread": 32.5785 }, "prompt_style": { "values": { "detailed": { "mean": 297.6667, - "effect": -19.7654, + "effect": -26.4175, "n": 30 }, "simple": { - "mean": 318.9843, - "effect": 1.5523, - "n": 382 + "mean": 326.1373, + "effect": 2.0532, + "n": 386 } }, - "spread": 21.3176 + "spread": 28.4706 }, "linter": { "values": { "off": { "mean": 326.6, - "effect": 9.168, + "effect": 2.5159, "n": 35 }, "on": { - "mean": 316.5809, - "effect": -0.8511, - "n": 377 - } - }, - "spread": 10.0191 - }, - "tool_read": { - "values": { - "off": { - "mean": 324.1034, - "effect": 6.6714, - "n": 29 - }, - "on": { - "mean": 316.9269, - "effect": -0.5051, - "n": 383 + "mean": 323.853, + "effect": -0.2311, + "n": 381 } }, - "spread": 7.1765 + "spread": 2.747 }, "human_language": { "values": { "en": { - "mean": 317.1429, - "effect": -0.2892, - "n": 385 + "mean": 324.2596, + "effect": 0.1755, + "n": 389 }, "es": { "mean": 321.5556, - "effect": 4.1235, + "effect": -2.5286, "n": 27 } }, - "spread": 4.4127 + "spread": 2.704 + }, + "tool_read": { + "values": { + "off": { + "mean": 324.1034, + "effect": 0.0193, + "n": 29 + }, + "on": { + "mean": 324.0827, + "effect": -0.0014, + "n": 387 + } + }, + "spread": 0.0207 } } \ No newline at end of file diff --git a/results/index.jsonl b/results/index.jsonl @@ -418,3 +418,7 @@ {"run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=svg_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3", "task": "tetris", "model": "glm-5.1", "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=svg_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", "short_id": "8cf0fbde", "short_cell_id": "a4c12deb", "completed_at": "2026-04-07T01:42:57.035119+00:00"} {"run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1", "task": "tetris", "model": "glm-5.1", "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", "short_id": "11b37482", "short_cell_id": "86ee3001", "completed_at": "2026-04-07T01:48:05.623201+00:00"} {"run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2", "task": "tetris", "model": "glm-5.1", "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", "short_id": "63c0c2ab", "short_cell_id": "86ee3001", "completed_at": "2026-04-07T01:48:29.575707+00:00"} +{"run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3", "task": "tetris", "model": "glm-5.1", "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", "short_id": "5da14018", "short_cell_id": "86ee3001", "completed_at": "2026-04-07T02:03:27.157428+00:00"} +{"run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1", "task": "tetris", "model": "glm-5.1", "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", "short_id": "16dab3d1", "short_cell_id": "cd8ad131", "completed_at": "2026-04-07T02:10:52.798576+00:00"} +{"run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2", "task": "tetris", "model": "glm-5.1", "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", "short_id": "14486646", "short_cell_id": "cd8ad131", "completed_at": "2026-04-07T02:19:50.929133+00:00"} +{"run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3", "task": "tetris", "model": "glm-5.1", "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", "short_id": "31d60e5a", "short_cell_id": "cd8ad131", "completed_at": "2026-04-07T02:31:00.258065+00:00"} diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/claude_output.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/claude_output.json @@ -0,0 +1 @@ +{"type":"result","subtype":"success","is_error":false,"duration_ms":775970,"duration_api_ms":770494,"num_turns":34,"result":"Everything is built. Here's a summary of what was created:\n\n## Files\n\n| File | Purpose |\n|---|---|\n| `src/index.html` | Game page with board, side panels, overlays |\n| `src/styles.css` | Dark theme styling, piece colors, animations |\n| `src/tetris.ts` | Complete game engine in TypeScript (816 lines) |\n| `src/tetris.js` | Compiled JavaScript output |\n| `tsconfig.json` | TypeScript configuration |\n\n## Game Features\n\n**Standard Tetris Mechanics:**\n- **7-bag randomizer** — fair piece distribution using Fisher-Yates shuffle\n- **SRS rotation system** — full Super Rotation System with wall kick tables for all 7 pieces (I, O, T, S, Z, J, L)\n- **Ghost piece** — translucent preview showing where the piece will land\n- **Hold piece** — stash a piece for later (once per drop)\n- **Next piece preview** — shows the upcoming piece\n- **Line clearing** with flash animation\n- **Combo scoring** — bonus points for consecutive clears\n- **NES-style scoring** — 100/300/500/800 × (level+1) for 1/2/3/4 lines, plus soft/hard drop bonuses\n- **20 speed levels** — drop interval decreases from 800ms down to 5ms\n\n**Controls:**\n| Key | Action |\n|---|---|\n| ← → | Move left/right |\n| ↑ / X | Rotate clockwise |\n| Z | Rotate counter-clockwise |\n| ↓ | Soft drop (+1 point per row) |\n| Space | Hard drop (+2 points per row) |\n| C / Shift | Hold piece |\n| P / Esc | Pause |\n\n**Rendering:** Entirely DOM-based — the 10×20 grid is CSS Grid with styled `<div>` cells, and piece colors are applied via CSS classes with inset box-shadow for a 3D beveled look.\n\nOpen `src/index.html` in a browser to play.","stop_reason":"end_turn","session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","total_cost_usd":0.5418750000000001,"usage":{"input_tokens":27875,"cache_creation_input_tokens":0,"cache_read_input_tokens":500800,"output_tokens":20534,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{"glm-5.1":{"inputTokens":27875,"outputTokens":20534,"cacheReadInputTokens":500800,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.5418750000000001,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"8af8145e-9c05-4f26-b4b6-966853a558cc"} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/eval_results.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/eval_results.json @@ -0,0 +1,265 @@ +{ + "structural": { + "pass": false, + "checks": [ + { + "name": "entry_point_exists", + "pass": false, + "detail": "no index.html found in workspace root, dist/, or public/" + }, + { + "name": "package_json_exists", + "pass": true, + "detail": "package.json found" + }, + { + "name": "build_succeeds", + "pass": true, + "detail": "no build script defined (static project)" + }, + { + "name": "typescript_compiles", + "pass": false, + "detail": "tsc --noEmit failed" + } + ], + "score": 0.5 + }, + "quality": { + "lint": { + "pass": true, + "errors": 0, + "warnings": 0 + }, + "typecheck": { + "pass": false, + "errors": 1 + }, + "performance": { + "pass": true, + "bundle_size_bytes": 0, + "size_under_512kb": true + }, + "score": 0.67 + }, + "code_analysis": { + "files": { + "total": 11, + "code": 8, + "docs": 0, + "unnecessary": 0, + "unnecessary_list": [] + }, + "lines_of_code": 2402, + "dependencies": { + "production": 0, + "dev": 4, + "total": 4 + }, + "complexity": "over-engineered", + "console_logs": 0, + "magic_numbers": { + "count": 75, + "excessive": true + }, + "function_length": { + "count": 97, + "average": 7.1, + "max": 46, + "long_functions": 0 + }, + "max_nesting_depth": 10, + "global_declarations": 66, + "naming": { + "dominant_style": "camelCase", + "consistency_pct": 100.0, + "camel_case": 829, + "snake_case": 0 + }, + "error_handling": { + "try_catch_blocks": 4, + "has_error_handling": true + }, + "comments": { + "comment_lines": 119, + "source_lines": 1802, + "ratio_pct": 6.6 + }, + "separation_of_concerns": { + "verdict": "mixed", + "files_with_rendering": 4, + "files_with_logic": 3, + "files_with_both": 3 + }, + "html_validation": { + "valid": true, + "errors": 0 + }, + "duplication_percentage": 0.0, + "score": 0.8 + }, + "transcript_analysis": { + "total_events": 97, + "tool_calls": { + "total": 33, + "bash": 11, + "write": 0, + "edit": 11, + "read": 11 + }, + "wasted_turns": { + "total": 0, + "docs": 0, + "ascii_art": 0, + "server_starts": 0 + }, + "errors_encountered": 0, + "thinking_blocks": 6, + "text_blocks": 21, + "productivity_ratio": 1.0, + "self_tested": false, + "score": 1.0 + }, + "gameplay_bot": { + "pass": false, + "score": 0.25, + "total": 16, + "passed": 4, + "failed": 12, + "report": { + "implementation": { + "renderer": "unknown", + "grid_detected": false, + "grid_bounds": null, + "controls": { + "left": "ArrowLeft", + "right": "ArrowRight", + "down": "ArrowDown", + "rotate": "ArrowUp", + "drop": "Space" + }, + "start_mechanism": "click_canvas", + "score_element_found": false, + "grid_confidence": 0 + }, + "tests": [ + { + "name": "game_loads", + "pass": true, + "detail": "no console errors" + }, + { + "name": "game_starts", + "pass": true, + "detail": "started via click_canvas" + }, + { + "name": "auto_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify auto-drop" + }, + { + "name": "move_left", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_right", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_down", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "rotate", + "pass": false, + "detail": "grid reader unreliable, cannot verify rotation" + }, + { + "name": "all_pieces_rotate", + "pass": false, + "detail": "could not detect any piece rotations via grid reader" + }, + { + "name": "hard_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify hard drop" + }, + { + "name": "piece_locks", + "pass": true, + "detail": "filled cells persist at bottom (grid-verified, 1 lock event(s))" + }, + { + "name": "new_piece_spawns", + "pass": false, + "detail": "could not detect new piece spawning at top via grid reader" + }, + { + "name": "multiple_pieces", + "pass": false, + "detail": "only 3 piece(s) detected, need at least 3" + }, + { + "name": "line_clear", + "pass": false, + "detail": "could not trigger or detect a line clear via grid reader" + }, + { + "name": "score_changes", + "pass": false, + "detail": "no score element found" + }, + { + "name": "game_over", + "pass": true, + "detail": "game stopped after stacking to top" + }, + { + "name": "playable_30s", + "pass": false, + "detail": "only played for 6s" + } + ], + "summary": { + "total": 16, + "passed": 4, + "failed": 12, + "score": 0.25 + }, + "gameplay": { + "pieces_placed": 16, + "lines_cleared": 0, + "max_score_observed": 0, + "play_duration_seconds": 6, + "errors_during_play": 0 + }, + "session": { + "frames": 23, + "events_count": 2, + "pieces_spawned": 0, + "pieces_locked": 3, + "lines_cleared": 0, + "piece_types_seen": [], + "grid_read_success_rate": 0 + }, + "performance": { + "load_time_ms": 19 + }, + "accessibility": { + "issues": [], + "issue_count": 0, + "pass": true + } + } + }, + "outcome_score": 0.125, + "score": 0.125, + "sonarqube": { + "error": "SonarQube not running at localhost:9000", + "score": 0 + } +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/gameplay-bot-report.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/gameplay-bot-report.json @@ -0,0 +1,129 @@ +{ + "implementation": { + "renderer": "unknown", + "grid_detected": false, + "grid_bounds": null, + "controls": { + "left": "ArrowLeft", + "right": "ArrowRight", + "down": "ArrowDown", + "rotate": "ArrowUp", + "drop": "Space" + }, + "start_mechanism": "click_canvas", + "score_element_found": false, + "grid_confidence": 0 + }, + "tests": [ + { + "name": "game_loads", + "pass": true, + "detail": "no console errors" + }, + { + "name": "game_starts", + "pass": true, + "detail": "started via click_canvas" + }, + { + "name": "auto_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify auto-drop" + }, + { + "name": "move_left", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_right", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_down", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "rotate", + "pass": false, + "detail": "grid reader unreliable, cannot verify rotation" + }, + { + "name": "all_pieces_rotate", + "pass": false, + "detail": "could not detect any piece rotations via grid reader" + }, + { + "name": "hard_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify hard drop" + }, + { + "name": "piece_locks", + "pass": true, + "detail": "filled cells persist at bottom (grid-verified, 1 lock event(s))" + }, + { + "name": "new_piece_spawns", + "pass": false, + "detail": "could not detect new piece spawning at top via grid reader" + }, + { + "name": "multiple_pieces", + "pass": false, + "detail": "only 3 piece(s) detected, need at least 3" + }, + { + "name": "line_clear", + "pass": false, + "detail": "could not trigger or detect a line clear via grid reader" + }, + { + "name": "score_changes", + "pass": false, + "detail": "no score element found" + }, + { + "name": "game_over", + "pass": true, + "detail": "game stopped after stacking to top" + }, + { + "name": "playable_30s", + "pass": false, + "detail": "only played for 6s" + } + ], + "summary": { + "total": 16, + "passed": 4, + "failed": 12, + "score": 0.25 + }, + "gameplay": { + "pieces_placed": 16, + "lines_cleared": 0, + "max_score_observed": 0, + "play_duration_seconds": 6, + "errors_during_play": 0 + }, + "session": { + "frames": 23, + "events_count": 2, + "pieces_spawned": 0, + "pieces_locked": 3, + "lines_cleared": 0, + "piece_types_seen": [], + "grid_read_success_rate": 0 + }, + "performance": { + "load_time_ms": 19 + }, + "accessibility": { + "issues": [], + "issue_count": 0, + "pass": true + } +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/meta.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/meta.json @@ -33,5 +33,8 @@ "short_cell_id": "86ee3001", "run_number": 3, "claude_version": "2.1.92 (Claude Code)", - "started_at": "2026-04-07T01:50:27.346969+00:00" + "started_at": "2026-04-07T01:50:27.346969+00:00", + "wall_time_seconds": 776, + "exit_code": 0, + "completed_at": "2026-04-07T02:03:27.157428+00:00" } \ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/transcript.jsonl b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=dom_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/transcript.jsonl @@ -1,3 +1,97 @@ {"type": "harness", "subtype": "config", "model": "glm-5.1", "effort": "high", "tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebSearch", "WebFetch"], "max_budget_usd": 2.0, "timeout_seconds": 1200, "task": "tetris", "language": "typescript", "prompt_style": "simple"} {"type": "user", "subtype": "prompt", "message": {"role": "user", "content": "Build a playable Tetris game that runs in a browser. It should have all the standard mechanics: piece rotation, line clearing, scoring, and increasing speed. Use keyboard controls for movement and rotation.\n\n\nUse TypeScript.\n\nRender the game using DOM elements (divs/tables) with CSS for styling. Do not use Canvas or SVG."}} {"type":"system","subtype":"init","cwd":"/tmp/loop-bench-4v9zg5jy","session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","tools":["Bash","Edit","Read"],"mcp_servers":[],"model":"glm-5.1","permissionMode":"dontAsk","slash_commands":["update-config","debug","simplify","batch","loop","schedule","claude-api","compact","context","cost","heapdump","init","review","security-review","insights"],"apiKeySource":"apiKeyHelper","claude_code_version":"2.1.92","output_style":"default","agents":["general-purpose","statusline-setup","Explore","Plan"],"skills":["update-config","debug","simplify","batch","loop","schedule","claude-api"],"plugins":[{"name":"claude-hud","path":"/root/.claude/plugins/marketplaces/claude-hud/","source":"claude-hud@claude-hud"},{"name":"rust-analyzer-lsp","path":"/root/.claude/plugins/marketplaces/claude-plugins-official/plugins/rust-analyzer-lsp","source":"rust-analyzer-lsp@claude-plugins-official"}],"uuid":"bf530bca-ec43-4813-b471-13a6e1530ead","fast_mode_state":"off"} +{"type":"assistant","message":{"id":"msg_202604070950316679411881064b22","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The user wants me to build a playable Tetris game that runs in a browser using TypeScript, rendered with DOM elements (divs/tables) with CSS styling. Let me plan out the structure:\n\n1. A TypeScript file for the game logic\n2. An HTML file to load everything\n3. CSS for styling\n\nSince this needs to run in a browser with TypeScript, I'll need to set up a simple project. Let me create:\n\n1. `index.html` - The main page\n2. `styles.css` - CSS styling\n3. `tetris.ts` - The game logic in TypeScript\n4. A way to compile TypeScript (I'll use `tsc` or include a tsconfig)\n\nLet me think about the Tetris game mechanics:\n\n- Standard 10x20 grid\n- 7 tetrominoes (I, O, T, S, Z, J, L)\n- Rotation (SRS or simple rotation)\n- Line clearing\n- Scoring (lines cleared → points)\n- Increasing speed (gravity increases with level)\n- Keyboard controls: left/right arrows for movement, up arrow for rotation, down arrow for soft drop, space for hard drop\n\nLet me structure this properly:\n\n**Game Board:** 10 columns × 20 rows\n**Pieces:** Each tetromino has 4 rotation states\n**Controls:**\n- Left/Right arrows: move piece\n- Up arrow: rotate\n- Down arrow: soft drop\n- Space: hard drop\n\n**Scoring:**\n- 1 line: 100 × level\n- 2 lines: 300 × level\n- 3 lines: 500 × level\n- 4 lines: 800 × level\n\n**Speed:** Starts at 1000ms, decreases with level\n\nLet me create the project structure.\n\nI'll use DOM elements - a grid of divs for the game board, with CSS classes for coloring.\n\nLet me write this out:\n\n```typescript\n// tetris.ts\n\n// Tetromino shapes and their rotations\n// Each piece is defined as a set of rotation states\n// Each rotation state is an array of [row, col] offsets\n\nconst COLS = 10;\nconst ROWS = 20;\n\n// Tetromino definitions - each piece has 4 rotation states\n// Defined as 4x4 grids\nconst TETROMINOES: Record<string, number[][][]> = {\n I: [\n [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],\n [[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]],\n [[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]],\n [[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]],\n ],\n O: [\n [[1,1],[1,1]],\n [[1,1],[1,1]],\n [[1,1],[1,1]],\n [[1,1],[1,1]],\n ],\n T: [\n [[0,1,0],[1,1,1],[0,0,0]],\n [[0,1,0],[0,1,1],[0,1,0]],\n [[0,0,0],[1,1,1],[0,1,0]],\n [[0,1,0],[1,1,0],[0,1,0]],\n ],\n S: [\n [[0,1,1],[1,1,0],[0,0,0]],\n [[0,1,0],[0,1,1],[0,0,1]],\n [[0,0,0],[0,1,1],[1,1,0]],\n [[1,0,0],[1,1,0],[0,1,0]],\n ],\n Z: [\n [[1,1,0],[0,1,1],[0,0,0]],\n [[0,0,1],[0,1,1],[0,1,0]],\n [[0,0,0],[1,1,0],[0,1,1]],\n [[0,1,0],[1,1,0],[1,0,0]],\n ],\n J: [\n [[1,0,0],[1,1,1],[0,0,0]],\n [[0,1,1],[0,1,0],[0,1,0]],\n [[0,0,0],[1,1,1],[0,0,1]],\n [[0,1,0],[0,1,0],[1,1,0]],\n ],\n L: [\n [[0,0,1],[1,1,1],[0,0,0]],\n [[0,1,0],[0,1,0],[0,1,1]],\n [[0,0,0],[1,1,1],[1,0,0]],\n [[1,1,0],[0,1,0],[0,1,0]],\n ],\n};\n```\n\nLet me think about the structure more carefully. I'll create a well-organized TypeScript project.\n\nActually, let me think about whether to use a bundler or just compile with tsc. The simplest approach would be:\n1. Write TypeScript\n2. Compile with `tsc`\n3. Include the compiled JS in HTML\n\nLet me create the project.\n\nI need to think about the game loop architecture:\n- The game has a board (2D array)\n- A current piece with position and rotation\n- A game loop that drops the piece down at intervals\n- Input handling for movement/rotation\n- Collision detection\n- Line clearing logic\n- Scoring and leveling\n\nFor the DOM rendering, I'll create a grid of divs and update their classes based on the board state.\n\nLet me create all the files now.\n\nFor the CSS, I want a nice retro Tetris look:\n- Dark background\n- Colored pieces (each tetromino type has its own color)\n- Grid lines\n- Score/level display\n- Next piece preview\n\nLet me finalize the color scheme:\n- I: cyan\n- O: yellow\n- T: purple\n- S: green\n- Z: red\n- J: blue\n- L: orange\n\nThis is the standard Tetris color scheme.\n\nLet me write all the code now.\n\nFor the project structure:\n```\n/tmp/loop-bench-4v9zg5jy/\n ├── index.html\n ├── styles.css\n ├── tetris.ts\n └── tsconfig.json\n```\n\nI'll compile with tsc and then serve the files.\n\nLet me write the full implementation now.\n\nActually, I should think about whether to use a table or divs. Let me use a grid of divs with CSS Grid. That's clean and modern.\n\nHere's my plan for the HTML:\n- A container with the game board, next piece preview, score/level/lines display\n- The game board is a CSS grid of 10×20 cells\n- Each cell is a div that can have a class for its color\n\nFor the TypeScript:\n- A TetrisGame class\n- Methods for: init, gameLoop, movePiece, rotatePiece, dropPiece, lockPiece, clearLines, updateScore, render, handleInput\n- Piece class/interface with type, rotation, position\n\nLet me write this all out now. I'll be thorough and make a complete, polished game.\n\nLet me think about the game state:\n```typescript\ninterface Piece {\n type: string; // 'I', 'O', 'T', 'S', 'Z', 'J', 'L'\n rotation: number; // 0-3\n row: number; // position on board\n col: number;\n}\n\ntype Board = (string | null)[][]; // null for empty, string for piece type\n```\n\nThe game flow:\n1. Spawn piece at top\n2. Game loop: every tick, try to move piece down\n3. If can't move down, lock piece\n4. Check for line clears\n5. Update score\n6. Check for game over\n7. Spawn next piece\n\nLet me also add a ghost piece (shadow showing where the piece will land) for better gameplay.\n\nAnd I should add:\n- Start screen\n- Game over screen\n- Pause functionality\n\nLet me write all this out now. I'll create a comprehensive implementation.\n\nFor the ghost piece, I'll compute where the current piece would land if hard-dropped, and show a semi-transparent version there.\n\nLet me structure the TypeScript code:\n\n```typescript\n// Constants\nconst COLS = 10;\nconst ROWS = 20;\nconst PIECE_TYPES = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];\n\n// Tetromino shapes (each has 4 rotations)\nconst SHAPES: Record<string, number[][][]> = { ... };\n\n// Colors for each piece type\nconst COLORS: Record<string, string> = {\n I: '#00f0f0',\n O: '#f0f000',\n T: '#a000f0',\n S: '#00f000',\n Z: '#f00000',\n J: '#0000f0',\n L: '#f0a000',\n};\n\n// Scoring\nconst LINE_SCORES = [0, 100, 300, 500, 800];\n\n// Speed (ms per drop) - decreases with level\nfunction getSpeed(level: number): number {\n return Math.max(100, 1000 - (level - 1) * 100);\n}\n\nclass TetrisGame {\n board: (string | null)[][];\n currentPiece: Piece | null;\n nextPiece: Piece | null;\n score: number;\n level: number;\n lines: number;\n gameOver: boolean;\n paused: boolean;\n lastDrop: number;\n animationFrame: number;\n \n constructor() { ... }\n \n init() { ... }\n \n createBoard(): (string | null)[][] { ... }\n \n randomPiece(): Piece { ... }\n \n getShape(piece: Piece): number[][] { ... }\n \n isValidPosition(piece: Piece, row?: number, col?: number, rotation?: number): boolean { ... }\n \n lockPiece() { ... }\n \n clearLines(): number { ... }\n \n moveLeft() { ... }\n moveRight() { ... }\n moveDown(): boolean { ... }\n rotate() { ... }\n hardDrop() { ... }\n \n getGhostPosition(): { row: number, col: number } { ... }\n \n gameLoop(timestamp: number) { ... }\n \n render() { ... }\n \n handleKeyDown(e: KeyboardEvent) { ... }\n \n start() { ... }\n \n reset() { ... }\n}\n```\n\nOK, I think I have a solid plan. Let me write the code now.\n\nActually, let me also think about the 7-bag randomizer. In modern Tetris, pieces are dealt in bags of 7 (one of each type), shuffled. This prevents long droughts of specific pieces. Let me implement that.\n\n```typescript\nprivate bag: string[] = [];\n\nprivate fillBag(): void {\n const pieces = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = pieces.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [pieces[i], pieces[j]] = [pieces[j], pieces[i]];\n }\n this.bag.push(...pieces);\n}\n\nprivate nextFromBag(): string {\n if (this.bag.length < 2) {\n this.fillBag();\n }\n return this.bag.shift()!;\n}\n```\n\nNow let me think about wall kicks. Simple rotation might cause issues near walls. I'll implement basic wall kick logic:\n- Try normal rotation\n- Try shifting left by 1\n- Try shifting right by 1\n- Try shifting left by 2 (for I piece)\n- Try shifting right by 2 (for I piece)\n\nThis is simpler than full SRS but works well enough.\n\nOK let me write all the code now.\n\nFor the DOM rendering, I'll have:\n- A grid container for the board (200 cells)\n- Each cell is a div with a class based on its contents\n- A separate grid for the next piece preview\n- Divs for score, level, lines\n\nThe render function will update the classes on all cells.\n\nLet me write the complete implementation:\n\nActually, I realize I should think about the overall visual layout. Let me design it:\n\n```\n+----------------------------------+\n| TETRIS |\n| |\n| +----------+ +---------+ |\n| | | | NEXT | |\n| | | | [piece] | |\n| | BOARD | +---------+ |\n| | 10x20 | | SCORE | |\n| | | | 0 | |\n| | | +---------+ |\n| | | | LEVEL | |\n| | | | 1 | |\n| | | +---------+ |\n| | | | LINES | |\n| | | | 0 | |\n| | | +---------+ |\n| +----------+ |\n| |\n| Controls: Arrow keys / Space |\n+----------------------------------+\n```\n\nOK, let me finalize everything and write the code.\n\nLet me think about the cell size. If each cell is 30px × 30px:\n- Board: 300px × 600px\n- That's a good size\n\nFor the next piece preview, I'll use a 4×4 grid of 25px cells.\n\nAlright, let me write all the files now. I'll start with the TypeScript, then HTML, then CSS.\n\nActually, for speed I should create all files in parallel. Let me write them all out.\n\nLet me write the full tetris.ts:\n\n```typescript\nconst COLS = 10;\nconst ROWS = 20;\nconst PIECE_TYPES = ['I', 'O', 'T', 'S', 'Z', 'J', 'L'];\n\nconst SHAPES: Record<string, number[][][]> = {\n I: [\n [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],\n [[0,0,1,0],[0,0,1,0],[0,0,1,0],[0,0,1,0]],\n [[0,0,0,0],[0,0,0,0],[1,1,1,1],[0,0,0,0]],\n [[0,1,0,0],[0,1,0,0],[0,1,0,0],[0,1,0,0]],\n ],\n O: [\n [[1,1],[1,1]],\n [[1,1],[1,1]],\n [[1,1],[1,1]],\n [[1,1],[1,1]],\n ],\n T: [\n [[0,1,0],[1,1,1],[0,0,0]],\n [[0,1,0],[0,1,1],[0,1,0]],\n [[0,0,0],[1,1,1],[0,1,0]],\n [[0,1,0],[1,1,0],[0,1,0]],\n ],\n S: [\n [[0,1,1],[1,1,0],[0,0,0]],\n [[0,1,0],[0,1,1],[0,0,1]],\n [[0,0,0],[0,1,1],[1,1,0]],\n [[1,0,0],[1,1,0],[0,1,0]],\n ],\n Z: [\n [[1,1,0],[0,1,1],[0,0,0]],\n [[0,0,1],[0,1,1],[0,1,0]],\n [[0,0,0],[1,1,0],[0,1,1]],\n [[0,1,0],[1,1,0],[1,0,0]],\n ],\n J: [\n [[1,0,0],[1,1,1],[0,0,0]],\n [[0,1,1],[0,1,0],[0,1,0]],\n [[0,0,0],[1,1,1],[0,0,1]],\n [[0,1,0],[0,1,0],[1,1,0]],\n ],\n L: [\n [[0,0,1],[1,1,1],[0,0,0]],\n [[0,1,0],[0,1,0],[0,1,1]],\n [[0,0,0],[1,1,1],[1,0,0]],\n [[1,1,0],[0,1,0],[0,1,0]],\n ],\n};\n\nconst COLORS: Record<string, string> = {\n I: '#00f0f0',\n O: '#f0f000',\n T: '#a000f0',\n S: '#00f000',\n Z: '#f00000',\n J: '#0000f0',\n L: '#f0a000',\n};\n\nconst LINE_SCORES = [0, 100, 300, 500, 800];\nconst LINES_PER_LEVEL = 10;\n\nfunction getSpeed(level: number): number {\n // Tetris NES-style speed curve\n const speeds = [800, 720, 630, 550, 470, 380, 300, 220, 150, 100, 80, 80, 80, 70, 70, 70, 50, 50, 50, 30];\n return speeds[Math.min(level - 1, speeds.length - 1)];\n}\n\ninterface Piece {\n type: string;\n rotation: number;\n row: number;\n col: number;\n}\n\nclass TetrisGame {\n private board: (string | null)[][];\n private currentPiece: Piece | null = null;\n private nextPieceType: string | null = null;\n private score = 0;\n private level = 1;\n private lines = 0;\n private gameOver = false;\n private paused = false;\n private started = false;\n private lastDrop = 0;\n private animationFrame = 0;\n private bag: string[] = [];\n private boardCells: HTMLDivElement[][] = [];\n private nextPieceCells: HTMLDivElement[][] = [];\n \n constructor() {\n this.board = this.createBoard();\n this.initDOM();\n this.setupInput();\n this.showStartScreen();\n }\n \n private createBoard(): (string | null)[][] {\n return Array.from({ length: ROWS }, () => Array(COLS).fill(null));\n }\n \n private initDOM() {\n // Board grid\n const boardEl = document.getElementById('board')!;\n boardEl.innerHTML = '';\n this.boardCells = [];\n for (let r = 0; r < ROWS; r++) {\n const row: HTMLDivElement[] = [];\n for (let c = 0; c < COLS; c++) {\n const cell = document.createElement('div');\n cell.className = 'cell';\n boardEl.appendChild(cell);\n row.push(cell);\n }\n this.boardCells.push(row);\n }\n \n // Next piece grid\n const nextEl = document.getElementById('next-piece')!;\n nextEl.innerHTML = '';\n this.nextPieceCells = [];\n for (let r = 0; r < 4; r++) {\n const row: HTMLDivElement[] = [];\n for (let c = 0; c < 4; c++) {\n const cell = document.createElement('div');\n cell.className = 'next-cell';\n nextEl.appendChild(cell);\n row.push(cell);\n }\n this.nextPieceCells.push(row);\n }\n }\n \n private setupInput() {\n document.addEventListener('keydown', (e) => this.handleKeyDown(e));\n }\n \n private handleKeyDown(e: KeyboardEvent) {\n if (this.gameOver) {\n if (e.key === 'Enter' || e.key === ' ') {\n this.reset();\n }\n return;\n }\n \n if (!this.started) {\n if (e.key === 'Enter' || e.key === ' ') {\n this.start();\n }\n return;\n }\n \n if (e.key === 'p' || e.key === 'P' || e.key === 'Escape') {\n this.togglePause();\n return;\n }\n \n if (this.paused) return;\n \n switch (e.key) {\n case 'ArrowLeft':\n e.preventDefault();\n this.moveLeft();\n break;\n case 'ArrowRight':\n e.preventDefault();\n this.moveRight();\n break;\n case 'ArrowDown':\n e.preventDefault();\n this.softDrop();\n break;\n case 'ArrowUp':\n e.preventDefault();\n this.rotate();\n break;\n case ' ':\n e.preventDefault();\n this.hardDrop();\n break;\n }\n }\n \n private fillBag() {\n const pieces = [...PIECE_TYPES];\n for (let i = pieces.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [pieces[i], pieces[j]] = [pieces[j], pieces[i]];\n }\n this.bag.push(...pieces);\n }\n \n private nextFromBag(): string {\n if (this.bag.length < 2) {\n this.fillBag();\n }\n return this.bag.shift()!;\n }\n \n private createPiece(type: string): Piece {\n const shape = SHAPES[type][0];\n const cols = shape[0].length;\n return {\n type,\n rotation: 0,\n row: type === 'I' ? -1 : 0,\n col: Math.floor((COLS - cols) / 2),\n };\n }\n \n private getShape(piece: Piece): number[][] {\n return SHAPES[piece.type][piece.rotation];\n }\n \n private isValidPosition(piece: Piece, rowOffset = 0, colOffset = 0, rotation?: number): boolean {\n const shape = SHAPES[piece.type][rotation !== undefined ? rotation : piece.rotation];\n const row = piece.row + rowOffset;\n const col = piece.col + colOffset;\n \n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (shape[r][c]) {\n const boardRow = row + r;\n const boardCol = col + c;\n \n // Check bounds\n if (boardCol < 0 || boardCol >= COLS || boardRow >= ROWS) {\n return false;\n }\n \n // Allow pieces to be above the board\n if (boardRow < 0) continue;\n \n // Check collision with locked pieces\n if (this.board[boardRow][boardCol] !== null) {\n return false;\n }\n }\n }\n }\n return true;\n }\n \n private lockPiece() {\n if (!this.currentPiece) return;\n \n const shape = this.getShape(this.currentPiece);\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (shape[r][c]) {\n const boardRow = this.currentPiece.row + r;\n const boardCol = this.currentPiece.col + c;\n if (boardRow < 0) {\n this.endGame();\n return;\n }\n this.board[boardRow][boardCol] = this.currentPiece.type;\n }\n }\n }\n \n // Check for line clears\n const cleared = this.clearLines();\n if (cleared > 0) {\n this.lines += cleared;\n this.score += LINE_SCORES[cleared] * this.level;\n this.level = Math.floor(this.lines / LINES_PER_LEVEL) + 1;\n this.updateStats();\n }\n \n // Spawn next piece\n this.spawnPiece();\n }\n \n private clearLines(): number {\n let cleared = 0;\n for (let r = ROWS - 1; r >= 0; r--) {\n if (this.board[r].every(cell => cell !== null)) {\n // Remove the line\n this.board.splice(r, 1);\n // Add empty line at top\n this.board.unshift(Array(COLS).fill(null));\n cleared++;\n r++; // Re-check this row since lines shifted down\n }\n }\n return cleared;\n }\n \n private spawnPiece() {\n if (!this.nextPieceType) {\n this.nextPieceType = this.nextFromBag();\n }\n \n this.currentPiece = this.createPiece(this.nextPieceType);\n this.nextPieceType = this.nextFromBag();\n \n // Check if the new piece can be placed\n if (!this.isValidPosition(this.currentPiece)) {\n this.endGame();\n }\n \n this.renderNextPiece();\n }\n \n private moveLeft() {\n if (!this.currentPiece) return;\n if (this.isValidPosition(this.currentPiece, 0, -1)) {\n this.currentPiece.col--;\n this.render();\n }\n }\n \n private moveRight() {\n if (!this.currentPiece) return;\n if (this.isValidPosition(this.currentPiece, 0, 1)) {\n this.currentPiece.col++;\n this.render();\n }\n }\n \n private moveDown(): boolean {\n if (!this.currentPiece) return false;\n if (this.isValidPosition(this.currentPiece, 1, 0)) {\n this.currentPiece.row++;\n return true;\n }\n return false;\n }\n \n private softDrop() {\n if (this.moveDown()) {\n this.score += 1;\n this.updateStats();\n this.render();\n }\n }\n \n private hardDrop() {\n if (!this.currentPiece) return;\n let dropped = 0;\n while (this.isValidPosition(this.currentPiece, 1, 0)) {\n this.currentPiece.row++;\n dropped++;\n }\n this.score += dropped * 2;\n this.updateStats();\n this.lockPiece();\n this.render();\n }\n \n private rotate() {\n if (!this.currentPiece) return;\n const newRotation = (this.currentPiece.rotation + 1) % 4;\n \n // Try normal rotation\n if (this.isValidPosition(this.currentPiece, 0, 0, newRotation)) {\n this.currentPiece.rotation = newRotation;\n this.render();\n return;\n }\n \n // Wall kicks: try shifting left/right\n const kicks = [-1, 1, -2, 2];\n for (const kick of kicks) {\n if (this.isValidPosition(this.currentPiece, 0, kick, newRotation)) {\n this.currentPiece.rotation = newRotation;\n this.currentPiece.col += kick;\n this.render();\n return;\n }\n }\n }\n \n private getGhostRow(): number {\n if (!this.currentPiece) return 0;\n let ghostRow = this.currentPiece.row;\n while (this.isValidPosition({ ...this.currentPiece, row: ghostRow + 1 }, 0, 0)) {\n ghostRow++;\n }\n return ghostRow;\n }\n \n private gameLoop = (timestamp: number) => {\n if (this.gameOver || this.paused || !this.started) return;\n \n const speed = getSpeed(this.level);\n if (timestamp - this.lastDrop >= speed) {\n if (!this.moveDown()) {\n this.lockPiece();\n }\n this.render();\n this.lastDrop = timestamp;\n }\n \n this.animationFrame = requestAnimationFrame(this.gameLoop);\n };\n \n private render() {\n // Create a display board that includes the ghost and current piece\n const displayBoard: (string | null)[][] = this.board.map(row => [...row]);\n \n if (this.currentPiece) {\n // Draw ghost piece\n const ghostRow = this.getGhostRow();\n const shape = this.getShape(this.currentPiece);\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (shape[r][c]) {\n const boardRow = ghostRow + r;\n const boardCol = this.currentPiece.col + c;\n if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) {\n if (displayBoard[boardRow][boardCol] === null) {\n displayBoard[boardRow][boardCol] = 'ghost';\n }\n }\n }\n }\n }\n \n // Draw current piece (overwrites ghost if overlapping)\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (shape[r][c]) {\n const boardRow = this.currentPiece.row + r;\n const boardCol = this.currentPiece.col + c;\n if (boardRow >= 0 && boardRow < ROWS && boardCol >= 0 && boardCol < COLS) {\n displayBoard[boardRow][boardCol] = this.currentPiece.type;\n }\n }\n }\n }\n }\n \n // Update DOM\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const cell = this.boardCells[r][c];\n const value = displayBoard[r][c];\n cell.className = 'cell';\n if (value === 'ghost') {\n cell.classList.add('ghost');\n } else if (value) {\n cell.classList.add('filled', value.toLowerCase());\n }\n }\n }\n }\n \n private renderNextPiece() {\n // Clear\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n this.nextPieceCells[r][c].className = 'next-cell';\n }\n }\n \n if (!this.nextPieceType) return;\n \n const shape = SHAPES[this.nextPieceType][0];\n // Center the piece in the 4x4 grid\n const offsetR = Math.floor((4 - shape.length) / 2);\n const offsetC = Math.floor((4 - shape[0].length) / 2);\n \n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (shape[r][c]) {\n const cell = this.nextPieceCells[r + offsetR][c + offsetC];\n cell.classList.add('filled', this.nextPieceType.toLowerCase());\n }\n }\n }\n }\n \n private updateStats() {\n document.getElementById('score-value')!.textContent = this.score.toString();\n document.getElementById('level-value')!.textContent = this.level.toString();\n document.getElementById('lines-value')!.textContent = this.lines.toString();\n }\n \n private showStartScreen() {\n const overlay = document.getElementById('overlay')!;\n overlay.classList.remove('hidden');\n overlay.querySelector('.overlay-title')!.textContent = 'TETRIS';\n overlay.querySelector('.overlay-message')!.textContent = 'Press ENTER or SPACE to start';\n }\n \n private showGameOver() {\n const overlay = document.getElementById('overlay')!;\n overlay.classList.remove('hidden');\n overlay.querySelector('.overlay-title')!.textContent = 'GAME OVER';\n overlay.querySelector('.overlay-message')!.textContent = `Score: ${this.score}\\nPress ENTER or SPACE to restart`;\n }\n \n private showPauseScreen() {\n const overlay = document.getElementById('overlay')!;\n overlay.classList.remove('hidden');\n overlay.querySelector('.overlay-title')!.textContent = 'PAUSED';\n overlay.querySelector('.overlay-message')!.textContent = 'Press P or ESC to resume';\n }\n \n private hideOverlay() {\n document.getElementById('overlay')!.classList.add('hidden');\n }\n \n private togglePause() {\n this.paused = !this.paused;\n if (this.paused) {\n this.showPauseScreen();\n } else {\n this.hideOverlay();\n this.animationFrame = requestAnimationFrame(this.gameLoop);\n }\n }\n \n private endGame() {\n this.gameOver = true;\n this.showGameOver();\n }\n \n start() {\n this.started = true;\n this.hideOverlay();\n this.spawnPiece();\n this.render();\n this.updateStats();\n this.lastDrop = performance.now();\n this.animationFrame = requestAnimationFrame(this.gameLoop);\n }\n \n reset() {\n cancelAnimationFrame(this.animationFrame);\n this.board = this.createBoard();\n this.currentPiece = null;\n this.nextPieceType = null;\n this.score = 0;\n this.level = 1;\n this.lines = 0;\n this.gameOver = false;\n this.paused = false;\n this.started = false;\n this.bag = [];\n this.render();\n this.renderNextPiece();\n this.updateStats();\n this.start();\n }\n}\n\n// Start the game when the page loads\nwindow.addEventListener('DOMContentLoaded', () => {\n new TetrisGame();\n});\n```\n\nHmm, let me reconsider the overlay structure. I need to have an overlay element with a title and message.\n\nLet me now write the HTML:\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Tetris</title>\n <link rel=\"stylesheet\" href=\"styles.css\">\n</head>\n<body>\n <div id=\"game-container\">\n <h1>TETRIS</h1>\n <div id=\"game-layout\">\n <div id=\"board-wrapper\">\n <div id=\"board\"></div>\n <div id=\"overlay\">\n <div class=\"overlay-content\">\n <div class=\"overlay-title\">TETRIS</div>\n <div class=\"overlay-message\">Press ENTER or SPACE to start</div>\n </div>\n </div>\n </div>\n <div id=\"side-panel\">\n <div class=\"panel-section\">\n <div class=\"panel-label\">NEXT</div>\n <div id=\"next-piece\"></div>\n </div>\n <div class=\"panel-section\">\n <div class=\"panel-label\">SCORE</div>\n <div id=\"score-value\" class=\"panel-value\">0</div>\n </div>\n <div class=\"panel-section\">\n <div class=\"panel-label\">LEVEL</div>\n <div id=\"level-value\" class=\"panel-value\">1</div>\n </div>\n <div class=\"panel-section\">\n <div class=\"panel-label\">LINES</div>\n <div id=\"lines-value\" class=\"panel-value\">0</div>\n </div>\n <div class=\"panel-section controls-info\">\n <div class=\"panel-label\">CONTROLS</div>\n <div class=\"controls-text\">\n ← → Move<br>\n ↑ Rotate<br>\n ↓ Soft Drop<br>\n Space Hard Drop<br>\n P / Esc Pause\n </div>\n </div>\n </div>\n </div>\n </div>\n <script src=\"tetris.js\"></script>\n</body>\n</html>\n```\n\nNow the CSS. I want a nice retro-inspired but modern look:\n\n```css\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbody {\n background: #0a0a0a;\n color: #fff;\n font-family: 'Courier New', monospace;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n overflow: hidden;\n}\n\n#game-container {\n text-align: center;\n}\n\nh1 {\n font-size: 2.5rem;\n margin-bottom: 20px;\n letter-spacing: 8px;\n text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);\n}\n\n#game-layout {\n display: flex;\n gap: 20px;\n justify-content: center;\n align-items: flex-start;\n}\n\n#board-wrapper {\n position: relative;\n border: 2px solid #333;\n background: #111;\n}\n\n#board {\n display: grid;\n grid-template-columns: repeat(10, 30px);\n grid-template-rows: repeat(20, ","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"f11e0c3f-2dd1-44e6-8d08-0a1c4cdac170"} +{"type":"assistant","message":{"id":"msg_20260407095403ef52254cc79c40d8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The user wants me to build a playable Tetris game that runs in a browser using TypeScript, rendered with DOM elements (divs/tables) with CSS styling. Let me plan this out:\n\n1. I need to create an HTML file, a CSS file, and a TypeScript file\n2. The game should have:\n - Standard 7 Tetris pieces (I, O, T, S, Z, J, L)\n - Piece rotation\n - Line clearing\n - Scoring\n - Increasing speed (level system)\n - Keyboard controls\n\nLet me create this as a single-page application. I'll need to:\n- Set up the TypeScript file with game logic\n- Create CSS for styling\n- Create HTML to load everything\n\nSince this runs in a browser, I'll need to compile TypeScript to JavaScript. Let me create a simple setup.\n\nLet me think about the structure:\n- `index.html` - Main HTML file\n- `styles.css` - CSS styling\n- `tetris.ts` - TypeScript source\n- `tsconfig.json` - TypeScript config\n\nFor the game board, I'll use a 10x20 grid of divs. Each cell will be styled with CSS.\n\nLet me write the full implementation.\n\nI'll structure the TypeScript code with:\n- A `Piece` class/type for representing tetrominoes\n- Game state management\n- Rendering functions that update DOM elements\n- Input handling\n- Game loop with requestAnimationFrame and timing\n\nLet me write this out properly.\n\nThe 7 standard Tetris pieces:\n- I: cyan\n- O: yellow\n- T: purple\n- S: green\n- Z: red\n- J: blue\n- L: orange\n\nEach piece has 4 rotation states. I'll use the standard SRS (Super Rotation System) or simplified rotation.\n\nFor the grid, I'll use a 2D array (10 columns x 20 rows visible + 2-4 hidden rows at top for spawning).\n\nLet me create the files.\n\nFor the rotation system, I'll store each piece as a matrix and rotate it by transposing and reversing rows.\n\nScoring system (NES-style):\n- 1 line: 40 * (level + 1)\n- 2 lines: 100 * (level + 1)\n- 3 lines: 300 * (level + 1)\n- 4 lines (Tetris): 1200 * (level + 1)\n\nLevel increases every 10 lines cleared.\n\nSpeed increases with level - I'll use a decreasing interval.\n\nControls:\n- Left/Right arrows: move piece\n- Up arrow: rotate\n- Down arrow: soft drop\n- Space: hard drop\n\nLet me build this.\n\nI'll need to compile the TypeScript. Let me check if tsc is available, or I can just use a CDN for the TypeScript compiler or write it in a way that can be compiled.\n\nActually, let me just write the TypeScript and compile it. Let me check what's available.\n\nLet me create the project structure first, then compile.\n\nI think the cleanest approach is:\n1. Create all source files\n2. Compile TypeScript to JavaScript\n3. The HTML loads the compiled JS\n\nLet me start building.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"934f0bd2-1345-44ac-b74f-bdade0a25b77"} +{"type":"assistant","message":{"id":"msg_20260407095403ef52254cc79c40d8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"I'll build a complete Tetris game with TypeScript, DOM-based rendering, and full game mechanics. Let me set up the project structure."}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"db5c2c54-2c3d-40ce-a9fb-deb05cb5d690"} +{"type":"assistant","message":{"id":"msg_20260407095403ef52254cc79c40d8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_b277f81e94d54202b54ccf85","name":"Bash","input":{"command":"ls /tmp/loop-bench-4v9zg5jy/","description":"Check current directory contents"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"23ad8a25-fb9d-4810-b349-f58dfcfdecc2"} +{"type":"assistant","message":{"id":"msg_20260407095403ef52254cc79c40d8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_c961781c7a1540039074d31d","name":"Bash","input":{"command":"which tsc 2>/dev/null || which npx 2>/dev/null || which node 2>/dev/null","description":"Check available TypeScript/Node tools"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"cc67c808-1c02-441b-afd6-73f9ed2942ea"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_c961781c7a1540039074d31d","type":"tool_result","content":"/usr/bin/tsc\n[rerun: b2]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"1fc3db81-0e82-406c-9f79-3503aacd6ac4","timestamp":"2026-04-07T01:54:38.115Z","tool_use_result":{"stdout":"/usr/bin/tsc","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_b277f81e94d54202b54ccf85","type":"tool_result","content":"node_modules\npackage.json\npackage-lock.json\ntests-few\ntests-full\n[rerun: b1]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"87b0d3de-b642-45d3-9c73-a737ed8a93d7","timestamp":"2026-04-07T01:54:38.116Z","tool_use_result":{"stdout":"node_modules\npackage.json\npackage-lock.json\ntests-few\ntests-full","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407095439553836626efc4f51","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"Good, `tsc` is available. Let me create the project files. I'll create:\n\n1. `tsconfig.json`\n2. `src/tetris.ts`\n3. `src/index.html`\n4. `src/styles.css`\n\nThen compile and we're done.\n\nLet me write a comprehensive Tetris implementation.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"102d6f21-055d-4b6e-84be-cf6c22e06ea0"} +{"type":"assistant","message":{"id":"msg_20260407095439553836626efc4f51","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Good, `tsc` is available. Let me create all the project files."}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"c3fc31db-8a4a-49ae-b612-7d512246f11d"} +{"type":"assistant","message":{"id":"msg_20260407095439553836626efc4f51","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_d4aa23f4c2094c1f8a48cce0","name":"Bash","input":{"command":"mkdir -p /tmp/loop-bench-4v9zg5jy/src","description":"Create source directory"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"a32f6c10-d096-488f-93fc-762f237de032"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_d4aa23f4c2094c1f8a48cce0","type":"tool_result","content":"[rerun: b3]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"ff3701e0-2c4b-4349-b0d4-8112d99c56f8","timestamp":"2026-04-07T01:54:50.367Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":true}} +{"type":"assistant","message":{"id":"msg_2026040709545184238a45b0ee492e","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_b52258b82cb64174a4aa89fe","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/tsconfig.json","old_string":"","new_string":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outDir\": \"src\",\n \"sourceMap\": false,\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"forceConsistentCasingInFileNames\": true\n },\n \"files\": [\"src/tetris.ts\"]\n}"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"45910eb6-b34c-4a06-966a-47bb166a2b2f"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_b52258b82cb64174a4aa89fe","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/tsconfig.json has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"707ece63-395d-4252-b92d-e34fbbfd1a43","timestamp":"2026-04-07T01:55:02.544Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/tsconfig.json","oldString":"","newString":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outDir\": \"src\",\n \"sourceMap\": false,\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"forceConsistentCasingInFileNames\": true\n },\n \"files\": [\"src/tetris.ts\"]\n}","originalFile":"","structuredPatch":[{"oldStart":1,"oldLines":0,"newStart":1,"newLines":13,"lines":["+{","+ \"compilerOptions\": {","+ \"target\": \"ES2020\",","+ \"module\": \"none\",","+ \"outDir\": \"src\",","+ \"sourceMap\": false,","+ \"strict\": true,","+ \"noImplicitAny\": true,","+ \"removeComments\": true,","+ \"forceConsistentCasingInFileNames\": true","+ },","+ \"files\": [\"src/tetris.ts\"]","+}","\\ No newline at end of file"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_20260407095503d647439cb9c14828","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_586e5b864a024761b4ab76a3","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/styles.css","old_string":"","new_string":"/* ===== Reset & Base ===== */\n*,\n*::before,\n*::after {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbody {\n background: #0a0a1a;\n color: #e0e0e0;\n font-family: \"Segoe UI\", system-ui, -apple-system, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n overflow: hidden;\n}\n\n/* ===== Layout ===== */\n.game-wrapper {\n display: flex;\n gap: 24px;\n align-items: flex-start;\n}\n\n.side-panel {\n display: flex;\n flex-direction: column;\n gap: 16px;\n width: 140px;\n}\n\n/* ===== Board ===== */\n.board-frame {\n background: linear-gradient(135deg, #1a1a3e, #12122a);\n border: 3px solid #3a3a6e;\n border-radius: 6px;\n padding: 4px;\n box-shadow: 0 0 30px rgba(80, 60, 200, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.5);\n}\n\n.board {\n display: grid;\n grid-template-columns: repeat(10, 28px);\n grid-template-rows: repeat(20, 28px);\n gap: 1px;\n background: #0d0d20;\n}\n\n.cell {\n width: 28px;\n height: 28px;\n background: #111128;\n border-radius: 2px;\n transition: background-color 0.05s;\n}\n\n.cell.filled {\n border-radius: 3px;\n box-shadow: inset 0 0 4px rgba(255, 255, 255, 0.15);\n}\n\n/* Piece colours */\n.cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600, inset 0 0 6px rgba(255,255,255,0.2); }\n\n/* Ghost piece */\n.cell.ghost {\n background: rgba(255, 255, 255, 0.08);\n border: 1px dashed rgba(255, 255, 255, 0.25);\n box-shadow: none;\n}\n\n/* Line clear flash */\n.cell.clearing {\n animation: line-clear 0.4s ease-out;\n}\n\n@keyframes line-clear {\n 0% { background: #fff; transform: scale(1.1); }\n 50% { background: #fff; transform: scale(1.05); }\n 100% { background: #111128; transform: scale(1); }\n}\n\n/* ===== Panel Boxes ===== */\n.panel-box {\n background: linear-gradient(135deg, #1a1a3e, #12122a);\n border: 2px solid #3a3a6e;\n border-radius: 6px;\n padding: 12px;\n text-align: center;\n}\n\n.panel-box h3 {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 2px;\n color: #8888bb;\n margin-bottom: 8px;\n}\n\n.panel-box .value {\n font-size: 22px;\n font-weight: 700;\n color: #ffffff;\n font-variant-numeric: tabular-nums;\n}\n\n/* ===== Next / Hold Previews ===== */\n.preview-grid {\n display: grid;\n grid-template-columns: repeat(4, 22px);\n grid-template-rows: repeat(4, 22px);\n gap: 1px;\n justify-content: center;\n margin: 0 auto;\n}\n\n.preview-cell {\n width: 22px;\n height: 22px;\n background: transparent;\n border-radius: 2px;\n}\n\n.preview-cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3; }\n.preview-cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300; }\n.preview-cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae; }\n.preview-cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300; }\n.preview-cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030; }\n.preview-cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3; }\n.preview-cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600; }\n\n/* ===== Controls Info ===== */\n.controls-box {\n font-size: 11px;\n line-height: 1.8;\n color: #9999bb;\n}\n\n.controls-box kbd {\n display: inline-block;\n background: #22224a;\n border: 1px solid #44447a;\n border-radius: 3px;\n padding: 1px 6px;\n font-family: inherit;\n font-size: 10px;\n color: #ccccee;\n min-width: 22px;\n text-align: center;\n}\n\n/* ===== Overlays ===== */\n.overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n background: rgba(5, 5, 20, 0.88);\n border-radius: 6px;\n z-index: 10;\n transition: opacity 0.3s;\n}\n\n.overlay.hidden {\n opacity: 0;\n pointer-events: none;\n}\n\n.overlay h1 {\n font-size: 28px;\n margin-bottom: 8px;\n letter-spacing: 4px;\n text-transform: uppercase;\n}\n\n.overlay h2 {\n font-size: 20px;\n margin-bottom: 16px;\n color: #ff4466;\n}\n\n.overlay .subtitle {\n font-size: 13px;\n color: #8888bb;\n margin-bottom: 20px;\n}\n\n.overlay .prompt {\n font-size: 14px;\n color: #aaaadd;\n animation: blink 1.2s ease-in-out infinite;\n}\n\n@keyframes blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.board-container {\n position: relative;\n}\n\n/* ===== Responsive ===== */\n@media (max-height: 640px) {\n .board {\n grid-template-columns: repeat(10, 22px);\n grid-template-rows: repeat(20, 22px);\n }\n .cell {\n width: 22px;\n height: 22px;\n }\n}\n"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"4494680a-2d2a-4979-adb9-c607eb72970a"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_586e5b864a024761b4ab76a3","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/styles.css has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"5158d7f2-fc8f-4bca-a52a-a6634409975b","timestamp":"2026-04-07T01:56:00.638Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/styles.css","oldString":"","newString":"/* ===== Reset & Base ===== */\n*,\n*::before,\n*::after {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbody {\n background: #0a0a1a;\n color: #e0e0e0;\n font-family: \"Segoe UI\", system-ui, -apple-system, sans-serif;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n overflow: hidden;\n}\n\n/* ===== Layout ===== */\n.game-wrapper {\n display: flex;\n gap: 24px;\n align-items: flex-start;\n}\n\n.side-panel {\n display: flex;\n flex-direction: column;\n gap: 16px;\n width: 140px;\n}\n\n/* ===== Board ===== */\n.board-frame {\n background: linear-gradient(135deg, #1a1a3e, #12122a);\n border: 3px solid #3a3a6e;\n border-radius: 6px;\n padding: 4px;\n box-shadow: 0 0 30px rgba(80, 60, 200, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.5);\n}\n\n.board {\n display: grid;\n grid-template-columns: repeat(10, 28px);\n grid-template-rows: repeat(20, 28px);\n gap: 1px;\n background: #0d0d20;\n}\n\n.cell {\n width: 28px;\n height: 28px;\n background: #111128;\n border-radius: 2px;\n transition: background-color 0.05s;\n}\n\n.cell.filled {\n border-radius: 3px;\n box-shadow: inset 0 0 4px rgba(255, 255, 255, 0.15);\n}\n\n/* Piece colours */\n.cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3, inset 0 0 6px rgba(255,255,255,0.2); }\n.cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600, inset 0 0 6px rgba(255,255,255,0.2); }\n\n/* Ghost piece */\n.cell.ghost {\n background: rgba(255, 255, 255, 0.08);\n border: 1px dashed rgba(255, 255, 255, 0.25);\n box-shadow: none;\n}\n\n/* Line clear flash */\n.cell.clearing {\n animation: line-clear 0.4s ease-out;\n}\n\n@keyframes line-clear {\n 0% { background: #fff; transform: scale(1.1); }\n 50% { background: #fff; transform: scale(1.05); }\n 100% { background: #111128; transform: scale(1); }\n}\n\n/* ===== Panel Boxes ===== */\n.panel-box {\n background: linear-gradient(135deg, #1a1a3e, #12122a);\n border: 2px solid #3a3a6e;\n border-radius: 6px;\n padding: 12px;\n text-align: center;\n}\n\n.panel-box h3 {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 2px;\n color: #8888bb;\n margin-bottom: 8px;\n}\n\n.panel-box .value {\n font-size: 22px;\n font-weight: 700;\n color: #ffffff;\n font-variant-numeric: tabular-nums;\n}\n\n/* ===== Next / Hold Previews ===== */\n.preview-grid {\n display: grid;\n grid-template-columns: repeat(4, 22px);\n grid-template-rows: repeat(4, 22px);\n gap: 1px;\n justify-content: center;\n margin: 0 auto;\n}\n\n.preview-cell {\n width: 22px;\n height: 22px;\n background: transparent;\n border-radius: 2px;\n}\n\n.preview-cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3; }\n.preview-cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300; }\n.preview-cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae; }\n.preview-cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300; }\n.preview-cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030; }\n.preview-cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3; }\n.preview-cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600; }\n\n/* ===== Controls Info ===== */\n.controls-box {\n font-size: 11px;\n line-height: 1.8;\n color: #9999bb;\n}\n\n.controls-box kbd {\n display: inline-block;\n background: #22224a;\n border: 1px solid #44447a;\n border-radius: 3px;\n padding: 1px 6px;\n font-family: inherit;\n font-size: 10px;\n color: #ccccee;\n min-width: 22px;\n text-align: center;\n}\n\n/* ===== Overlays ===== */\n.overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n background: rgba(5, 5, 20, 0.88);\n border-radius: 6px;\n z-index: 10;\n transition: opacity 0.3s;\n}\n\n.overlay.hidden {\n opacity: 0;\n pointer-events: none;\n}\n\n.overlay h1 {\n font-size: 28px;\n margin-bottom: 8px;\n letter-spacing: 4px;\n text-transform: uppercase;\n}\n\n.overlay h2 {\n font-size: 20px;\n margin-bottom: 16px;\n color: #ff4466;\n}\n\n.overlay .subtitle {\n font-size: 13px;\n color: #8888bb;\n margin-bottom: 20px;\n}\n\n.overlay .prompt {\n font-size: 14px;\n color: #aaaadd;\n animation: blink 1.2s ease-in-out infinite;\n}\n\n@keyframes blink {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.4; }\n}\n\n.board-container {\n position: relative;\n}\n\n/* ===== Responsive ===== */\n@media (max-height: 640px) {\n .board {\n grid-template-columns: repeat(10, 22px);\n grid-template-rows: repeat(20, 22px);\n }\n .cell {\n width: 22px;\n height: 22px;\n }\n}\n","originalFile":"","structuredPatch":[{"oldStart":1,"oldLines":0,"newStart":1,"newLines":224,"lines":["+/* ===== Reset & Base ===== */","+*,","+*::before,","+*::after {","+ margin: 0;","+ padding: 0;","+ box-sizing: border-box;","+}","+","+body {","+ background: #0a0a1a;","+ color: #e0e0e0;","+ font-family: \"Segoe UI\", system-ui, -apple-system, sans-serif;","+ display: flex;","+ justify-content: center;","+ align-items: center;","+ min-height: 100vh;","+ overflow: hidden;","+}","+","+/* ===== Layout ===== */","+.game-wrapper {","+ display: flex;","+ gap: 24px;","+ align-items: flex-start;","+}","+","+.side-panel {","+ display: flex;","+ flex-direction: column;","+ gap: 16px;","+ width: 140px;","+}","+","+/* ===== Board ===== */","+.board-frame {","+ background: linear-gradient(135deg, #1a1a3e, #12122a);","+ border: 3px solid #3a3a6e;","+ border-radius: 6px;","+ padding: 4px;","+ box-shadow: 0 0 30px rgba(80, 60, 200, 0.15), inset 0 0 20px rgba(0, 0, 0, 0.5);","+}","+","+.board {","+ display: grid;","+ grid-template-columns: repeat(10, 28px);","+ grid-template-rows: repeat(20, 28px);","+ gap: 1px;","+ background: #0d0d20;","+}","+","+.cell {","+ width: 28px;","+ height: 28px;","+ background: #111128;","+ border-radius: 2px;","+ transition: background-color 0.05s;","+}","+","+.cell.filled {","+ border-radius: 3px;","+ box-shadow: inset 0 0 4px rgba(255, 255, 255, 0.15);","+}","+","+/* Piece colours */","+.cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3, inset 0 0 6px rgba(255,255,255,0.2); }","+.cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300, inset 0 0 6px rgba(255,255,255,0.2); }","+.cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae, inset 0 0 6px rgba(255,255,255,0.2); }","+.cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300, inset 0 0 6px rgba(255,255,255,0.2); }","+.cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030, inset 0 0 6px rgba(255,255,255,0.2); }","+.cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3, inset 0 0 6px rgba(255,255,255,0.2); }","+.cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600, inset 0 0 6px rgba(255,255,255,0.2); }","+","+/* Ghost piece */","+.cell.ghost {","+ background: rgba(255, 255, 255, 0.08);","+ border: 1px dashed rgba(255, 255, 255, 0.25);","+ box-shadow: none;","+}","+","+/* Line clear flash */","+.cell.clearing {","+ animation: line-clear 0.4s ease-out;","+}","+","+@keyframes line-clear {","+ 0% { background: #fff; transform: scale(1.1); }","+ 50% { background: #fff; transform: scale(1.05); }","+ 100% { background: #111128; transform: scale(1); }","+}","+","+/* ===== Panel Boxes ===== */","+.panel-box {","+ background: linear-gradient(135deg, #1a1a3e, #12122a);","+ border: 2px solid #3a3a6e;","+ border-radius: 6px;","+ padding: 12px;","+ text-align: center;","+}","+","+.panel-box h3 {","+ font-size: 11px;","+ text-transform: uppercase;","+ letter-spacing: 2px;","+ color: #8888bb;","+ margin-bottom: 8px;","+}","+","+.panel-box .value {","+ font-size: 22px;","+ font-weight: 700;","+ color: #ffffff;","+ font-variant-numeric: tabular-nums;","+}","+","+/* ===== Next / Hold Previews ===== */","+.preview-grid {","+ display: grid;","+ grid-template-columns: repeat(4, 22px);","+ grid-template-rows: repeat(4, 22px);","+ gap: 1px;","+ justify-content: center;","+ margin: 0 auto;","+}","+","+.preview-cell {","+ width: 22px;","+ height: 22px;","+ background: transparent;","+ border-radius: 2px;","+}","+","+.preview-cell.I { background: #00e5ff; box-shadow: inset 0 -2px 0 #009db3; }","+.preview-cell.O { background: #ffea00; box-shadow: inset 0 -2px 0 #b3a300; }","+.preview-cell.T { background: #d500f9; box-shadow: inset 0 -2px 0 #9500ae; }","+.preview-cell.S { background: #76ff03; box-shadow: inset 0 -2px 0 #53b300; }","+.preview-cell.Z { background: #ff1744; box-shadow: inset 0 -2px 0 #b31030; }","+.preview-cell.J { background: #2979ff; box-shadow: inset 0 -2px 0 #1c56b3; }","+.preview-cell.L { background: #ff9100; box-shadow: inset 0 -2px 0 #b36600; }","+","+/* ===== Controls Info ===== */","+.controls-box {","+ font-size: 11px;","+ line-height: 1.8;","+ color: #9999bb;","+}","+","+.controls-box kbd {","+ display: inline-block;","+ background: #22224a;","+ border: 1px solid #44447a;","+ border-radius: 3px;","+ padding: 1px 6px;","+ font-family: inherit;","+ font-size: 10px;","+ color: #ccccee;","+ min-width: 22px;","+ text-align: center;","+}","+","+/* ===== Overlays ===== */","+.overlay {","+ position: absolute;","+ inset: 0;","+ display: flex;","+ flex-direction: column;","+ justify-content: center;","+ align-items: center;","+ background: rgba(5, 5, 20, 0.88);","+ border-radius: 6px;","+ z-index: 10;","+ transition: opacity 0.3s;","+}","+","+.overlay.hidden {","+ opacity: 0;","+ pointer-events: none;","+}","+","+.overlay h1 {","+ font-size: 28px;","+ margin-bottom: 8px;","+ letter-spacing: 4px;","+ text-transform: uppercase;","+}","+","+.overlay h2 {","+ font-size: 20px;","+ margin-bottom: 16px;","+ color: #ff4466;","+}","+","+.overlay .subtitle {","+ font-size: 13px;","+ color: #8888bb;","+ margin-bottom: 20px;","+}","+","+.overlay .prompt {","+ font-size: 14px;","+ color: #aaaadd;","+ animation: blink 1.2s ease-in-out infinite;","+}","+","+@keyframes blink {","+ 0%, 100% { opacity: 1; }","+ 50% { opacity: 0.4; }","+}","+","+.board-container {","+ position: relative;","+}","+","+/* ===== Responsive ===== */","+@media (max-height: 640px) {","+ .board {","+ grid-template-columns: repeat(10, 22px);","+ grid-template-rows: repeat(20, 22px);","+ }","+ .cell {","+ width: 22px;","+ height: 22px;","+ }","+}"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_202604070956016a712774b28c47aa","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now the main TypeScript game file:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"62b18590-5101-475f-8895-b501412ce139"} +{"type":"assistant","message":{"id":"msg_202604070956016a712774b28c47aa","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_55956ff86732477d893fe29b","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","old_string":"","new_string":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"0fb2e13a-6bbf-442f-ba0b-e0fbaf10462e"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_55956ff86732477d893fe29b","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/tetris.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"b9e2edaf-d16f-4af0-86c3-b12dd4fb3701","timestamp":"2026-04-07T01:58:35.891Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","oldString":"","newString":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","originalFile":"","structuredPatch":[{"oldStart":1,"oldLines":0,"newStart":1,"newLines":803,"lines":["+// ============================================================","+// Tetris – DOM-rendered, TypeScript","+// ============================================================","+","+// ----- Types & Constants -----","+","+type PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";","+","+interface Position {","+ x: number;","+ y: number;","+}","+","+interface ActivePiece {","+ type: PieceType;","+ shape: number[][];","+ pos: Position;","+ rotation: number;","+}","+","+const COLS = 10;","+const ROWS = 20;","+const HIDDEN_ROWS = 2; // buffer rows above visible area","+","+// Shapes stored as 4×4 (or smaller) matrices per rotation state","+// Using the SRS-like representations","+const SHAPES: Record<PieceType, number[][][]> = {","+ I: [","+ [","+ [0, 0, 0, 0],","+ [1, 1, 1, 1],","+ [0, 0, 0, 0],","+ [0, 0, 0, 0],","+ ],","+ [","+ [0, 0, 1, 0],","+ [0, 0, 1, 0],","+ [0, 0, 1, 0],","+ [0, 0, 1, 0],","+ ],","+ [","+ [0, 0, 0, 0],","+ [0, 0, 0, 0],","+ [1, 1, 1, 1],","+ [0, 0, 0, 0],","+ ],","+ [","+ [0, 1, 0, 0],","+ [0, 1, 0, 0],","+ [0, 1, 0, 0],","+ [0, 1, 0, 0],","+ ],","+ ],","+ O: [","+ [","+ [1, 1],","+ [1, 1],","+ ],","+ [","+ [1, 1],","+ [1, 1],","+ ],","+ [","+ [1, 1],","+ [1, 1],","+ ],","+ [","+ [1, 1],","+ [1, 1],","+ ],","+ ],","+ T: [","+ [","+ [0, 1, 0],","+ [1, 1, 1],","+ [0, 0, 0],","+ ],","+ [","+ [0, 1, 0],","+ [0, 1, 1],","+ [0, 1, 0],","+ ],","+ [","+ [0, 0, 0],","+ [1, 1, 1],","+ [0, 1, 0],","+ ],","+ [","+ [0, 1, 0],","+ [1, 1, 0],","+ [0, 1, 0],","+ ],","+ ],","+ S: [","+ [","+ [0, 1, 1],","+ [1, 1, 0],","+ [0, 0, 0],","+ ],","+ [","+ [0, 1, 0],","+ [0, 1, 1],","+ [0, 0, 1],","+ ],","+ [","+ [0, 0, 0],","+ [0, 1, 1],","+ [1, 1, 0],","+ ],","+ [","+ [1, 0, 0],","+ [1, 1, 0],","+ [0, 1, 0],","+ ],","+ ],","+ Z: [","+ [","+ [1, 1, 0],","+ [0, 1, 1],","+ [0, 0, 0],","+ ],","+ [","+ [0, 0, 1],","+ [0, 1, 1],","+ [0, 1, 0],","+ ],","+ [","+ [0, 0, 0],","+ [1, 1, 0],","+ [0, 1, 1],","+ ],","+ [","+ [0, 1, 0],","+ [1, 1, 0],","+ [1, 0, 0],","+ ],","+ ],","+ J: [","+ [","+ [1, 0, 0],","+ [1, 1, 1],","+ [0, 0, 0],","+ ],","+ [","+ [0, 1, 1],","+ [0, 1, 0],","+ [0, 1, 0],","+ ],","+ [","+ [0, 0, 0],","+ [1, 1, 1],","+ [0, 0, 1],","+ ],","+ [","+ [0, 1, 0],","+ [0, 1, 0],","+ [1, 1, 0],","+ ],","+ ],","+ L: [","+ [","+ [0, 0, 1],","+ [1, 1, 1],","+ [0, 0, 0],","+ ],","+ [","+ [0, 1, 0],","+ [0, 1, 0],","+ [0, 1, 1],","+ ],","+ [","+ [0, 0, 0],","+ [1, 1, 1],","+ [1, 0, 0],","+ ],","+ [","+ [1, 1, 0],","+ [0, 1, 0],","+ [0, 1, 0],","+ ],","+ ],","+};","+","+// Wall kick data (SRS standard)","+// For J, L, S, T, Z pieces:","+const WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {","+ \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],","+ \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],","+ \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],","+ \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],","+ \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],","+ \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],","+ \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],","+ \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],","+};","+","+// For I piece:","+const WALL_KICKS_I: Record<string, [number, number][]> = {","+ \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],","+ \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],","+ \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],","+ \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],","+ \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],","+ \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],","+ \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],","+ \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],","+};","+","+// Scoring table (NES-style)","+const LINE_SCORES: Record<number, number> = {","+ 1: 100,","+ 2: 300,","+ 3: 500,","+ 4: 800,","+};","+","+const PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];","+","+// ----- DOM Setup -----","+","+function createCell(className: string): HTMLDivElement {","+ const div = document.createElement(\"div\");","+ div.className = className;","+ return div;","+}","+","+function buildBoardDOM(): HTMLDivElement {","+ const boardEl = document.getElementById(\"board\") as HTMLDivElement;","+ boardEl.innerHTML = \"\";","+ for (let r = 0; r < ROWS; r++) {","+ for (let c = 0; c < COLS; c++) {","+ boardEl.appendChild(createCell(\"cell\"));","+ }","+ }","+ return boardEl;","+}","+","+function buildPreviewDOM(id: string): HTMLDivElement {","+ const el = document.getElementById(id) as HTMLDivElement;","+ el.innerHTML = \"\";","+ for (let r = 0; r < 4; r++) {","+ for (let c = 0; c < 4; c++) {","+ el.appendChild(createCell(\"preview-cell\"));","+ }","+ }","+ return el;","+}","+","+// ----- Game State -----","+","+let board: (PieceType | null)[][];","+let cells: HTMLDivElement[];","+let nextPreviewCells: HTMLDivElement[];","+let holdPreviewCells: HTMLDivElement[];","+","+let currentPiece: ActivePiece | null;","+let nextType: PieceType;","+let holdType: PieceType | null;","+let canHold: boolean;","+","+let score: number;","+let level: number;","+let linesCleared: number;","+let combo: number;","+","+let gameOver: boolean;","+let paused: boolean;","+let started: boolean;","+","+let dropTimer: number;","+let lastTime: number;","+let animFrameId: number;","+","+// Bag randomizer for fair piece distribution","+let bag: PieceType[] = [];","+","+// ----- Initialization -----","+","+function initBoard(): void {","+ board = [];","+ for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {","+ board.push(new Array<PieceType | null>(COLS).fill(null));","+ }","+}","+","+function initGame(): void {","+ initBoard();","+ score = 0;","+ level = 0;","+ linesCleared = 0;","+ combo = -1;","+ gameOver = false;","+ paused = false;","+ holdType = null;","+ canHold = true;","+ bag = [];","+ nextType = pullFromBag();","+ currentPiece = null;","+ dropTimer = 0;","+ lastTime = 0;","+ spawnPiece();","+}","+","+// ----- Bag Randomizer -----","+","+function fillBag(): void {","+ const b = [...PIECE_TYPES];","+ // Fisher-Yates shuffle","+ for (let i = b.length - 1; i > 0; i--) {","+ const j = Math.floor(Math.random() * (i + 1));","+ [b[i], b[j]] = [b[j], b[i]];","+ }","+ bag = b;","+}","+","+function pullFromBag(): PieceType {","+ if (bag.length === 0) fillBag();","+ return bag.pop()!;","+}","+","+// ----- Piece Spawning -----","+","+function spawnPiece(): void {","+ const type = nextType;","+ nextType = pullFromBag();","+ canHold = true;","+","+ const shape = SHAPES[type][0];","+ // Spawn centered, in the hidden rows area","+ const x = Math.floor((COLS - shape[0].length) / 2);","+ const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area","+","+ currentPiece = { type, shape, pos: { x, y }, rotation: 0 };","+","+ if (!isValid(shape, currentPiece.pos)) {","+ // Game over – can't spawn","+ gameOver = true;","+ currentPiece = null;","+ showOverlay(\"gameover\");","+ }","+","+ renderPreview(nextPreviewCells, nextType);","+ renderHoldPreview();","+}","+","+// ----- Collision Detection -----","+","+function isValid(shape: number[][], pos: Position): boolean {","+ for (let r = 0; r < shape.length; r++) {","+ for (let c = 0; c < shape[r].length; c++) {","+ if (!shape[r][c]) continue;","+ const bx = pos.x + c;","+ const by = pos.y + r;","+ if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;","+ if (by >= 0 && board[by][bx] !== null) return false;","+ }","+ }","+ return true;","+}","+","+// ----- Movement -----","+","+function movePiece(dx: number, dy: number): boolean {","+ if (!currentPiece) return false;","+ const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };","+ if (isValid(currentPiece.shape, newPos)) {","+ currentPiece.pos = newPos;","+ return true;","+ }","+ return false;","+}","+","+function moveDown(): boolean {","+ if (!currentPiece) return false;","+ if (movePiece(0, 1)) return true;","+ lockPiece();","+ return false;","+}","+","+// ----- Rotation with Wall Kicks (SRS) -----","+","+function rotatePiece(dir: 1 | -1): void {","+ if (!currentPiece) return;","+ const oldRot = currentPiece.rotation;","+ const newRot = (oldRot + dir + 4) % 4;","+ const newShape = SHAPES[currentPiece.type][newRot];","+","+ const kickKey = `${oldRot}>${newRot}`;","+ const kicks = currentPiece.type === \"I\"","+ ? (WALL_KICKS_I[kickKey] || [[0, 0]])","+ : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);","+","+ for (const [kx, ky] of kicks) {","+ const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };","+ if (isValid(newShape, testPos)) {","+ currentPiece.shape = newShape;","+ currentPiece.pos = testPos;","+ currentPiece.rotation = newRot;","+ return;","+ }","+ }","+ // Rotation failed – no valid kick","+}","+","+// ----- Hard Drop -----","+","+function hardDrop(): void {","+ if (!currentPiece) return;","+ let dropped = 0;","+ while (movePiece(0, 1)) {","+ dropped++;","+ }","+ score += dropped * 2;","+ lockPiece();","+}","+","+// ----- Ghost Piece Position -----","+","+function getGhostY(): number {","+ if (!currentPiece) return 0;","+ let ghostY = currentPiece.pos.y;","+ const testPos = { x: currentPiece.pos.x, y: ghostY };","+ while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {","+ testPos.y++;","+ }","+ return testPos.y;","+}","+","+// ----- Lock Piece & Line Clear -----","+","+function lockPiece(): void {","+ if (!currentPiece) return;","+ const { shape, pos, type } = currentPiece;","+","+ for (let r = 0; r < shape.length; r++) {","+ for (let c = 0; c < shape[r].length; c++) {","+ if (!shape[r][c]) continue;","+ const by = pos.y + r;","+ const bx = pos.x + c;","+ if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {","+ board[by][bx] = type;","+ }","+ }","+ }","+","+ currentPiece = null;","+","+ // Check for completed lines","+ const clearedRows: number[] = [];","+ for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {","+ if (board[r].every((cell) => cell !== null)) {","+ clearedRows.push(r);","+ }","+ }","+","+ if (clearedRows.length > 0) {","+ combo++;","+ // Animate then remove","+ animateClear(clearedRows, () => {","+ removeLines(clearedRows);","+ const n = clearedRows.length;","+ const baseScore = LINE_SCORES[n] || 0;","+ score += baseScore * (level + 1);","+ // Combo bonus","+ if (combo > 0) {","+ score += 50 * combo * (level + 1);","+ }","+ linesCleared += n;","+ level = Math.floor(linesCleared / 10);","+ updateStats();","+ spawnPiece();","+ });","+ } else {","+ combo = -1;","+ updateStats();","+ spawnPiece();","+ }","+}","+","+function animateClear(rows: number[], callback: () => void): void {","+ // Mark cells as clearing","+ for (const r of rows) {","+ for (let c = 0; c < COLS; c++) {","+ const boardRow = r - HIDDEN_ROWS;","+ if (boardRow >= 0 && boardRow < ROWS) {","+ const idx = boardRow * COLS + c;","+ cells[idx].classList.add(\"clearing\");","+ }","+ }","+ }","+ setTimeout(callback, 400);","+}","+","+function removeLines(rows: number[]): void {","+ // Sort descending so we remove from bottom up","+ rows.sort((a, b) => b - a);","+ for (const r of rows) {","+ board.splice(r, 1);","+ board.unshift(new Array<PieceType | null>(COLS).fill(null));","+ }","+}","+","+// ----- Hold -----","+","+function holdPiece(): void {","+ if (!currentPiece || !canHold) return;","+ canHold = false;","+ const type = currentPiece.type;","+ if (holdType !== null) {","+ const prevHold = holdType;","+ holdType = type;","+ // Spawn the held piece","+ currentPiece = null;","+ const shape = SHAPES[prevHold][0];","+ const x = Math.floor((COLS - shape[0].length) / 2);","+ const y = HIDDEN_ROWS - shape.length;","+ currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };","+ } else {","+ holdType = type;","+ currentPiece = null;","+ spawnPiece();","+ }","+ renderHoldPreview();","+}","+","+// ----- Rendering -----","+","+function render(): void {","+ // Clear all cells","+ for (let r = 0; r < ROWS; r++) {","+ for (let c = 0; c < COLS; c++) {","+ const idx = r * COLS + c;","+ const cell = cells[idx];","+ const boardR = r + HIDDEN_ROWS;","+ // Remove dynamic classes","+ cell.className = \"cell\";","+ const value = board[boardR][c];","+ if (value) {","+ cell.classList.add(\"filled\", value);","+ }","+ }","+ }","+","+ // Render ghost piece","+ if (currentPiece) {","+ const ghostY = getGhostY();","+ if (ghostY !== currentPiece.pos.y) {","+ renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);","+ }","+ // Render current piece","+ renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);","+ }","+}","+","+function renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {","+ for (let r = 0; r < shape.length; r++) {","+ for (let c = 0; c < shape[r].length; c++) {","+ if (!shape[r][c]) continue;","+ const by = pos.y + r;","+ const bx = pos.x + c;","+ const boardRow = by - HIDDEN_ROWS;","+ if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {","+ const idx = boardRow * COLS + bx;","+ cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();","+ if (cls === \"ghost\") {","+ cells[idx].className = \"cell ghost\";","+ }","+ }","+ }","+ }","+}","+","+function renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {","+ const shape = SHAPES[type][0];","+ // Center the shape in the 4×4 preview","+ const offR = Math.floor((4 - shape.length) / 2);","+ const offC = Math.floor((4 - shape[0].length) / 2);","+ for (let r = 0; r < 4; r++) {","+ for (let c = 0; c < 4; c++) {","+ const idx = r * 4 + c;","+ previewCells[idx].className = \"preview-cell\";","+ const sr = r - offR;","+ const sc = c - offC;","+ if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {","+ previewCells[idx].classList.add(type);","+ }","+ }","+ }","+}","+","+function renderHoldPreview(): void {","+ if (holdType) {","+ renderPreview(holdPreviewCells, holdType);","+ } else {","+ for (const cell of holdPreviewCells) {","+ cell.className = \"preview-cell\";","+ }","+ }","+}","+","+function updateStats(): void {","+ document.getElementById(\"score-value\")!.textContent = score.toLocaleString();","+ document.getElementById(\"level-value\")!.textContent = String(level + 1);","+ document.getElementById(\"lines-value\")!.textContent = String(linesCleared);","+}","+","+// ----- Drop Speed -----","+","+function getDropInterval(): number {","+ // Speed curve (milliseconds per drop), gets faster each level","+ // Based roughly on NES Tetris speed curve","+ const speeds = [","+ 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,","+ 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,","+ ];","+ if (level < speeds.length) return speeds[level];","+ return 5;","+}","+","+// ----- Game Loop -----","+","+function gameLoop(timestamp: number): void {","+ if (gameOver || paused || !started) return;","+","+ if (lastTime === 0) lastTime = timestamp;","+ const delta = timestamp - lastTime;","+ lastTime = timestamp;","+","+ dropTimer += delta;","+ const interval = getDropInterval();","+","+ if (dropTimer >= interval) {","+ dropTimer -= interval;","+ moveDown();","+ }","+","+ render();","+ animFrameId = requestAnimationFrame(gameLoop);","+}","+","+// ----- Input Handling -----","+","+// DAS (Delayed Auto Shift) state","+let dasKey: string | null = null;","+let dasTimer: number = 0;","+let dasActive: boolean = false;","+const DAS_DELAY = 170; // ms before auto-repeat starts","+const DAS_RATE = 50; // ms between auto-repeats","+","+function handleKeyDown(e: KeyboardEvent): void {","+ if (!started || gameOver) {","+ if (e.key === \"Enter\" || e.key === \" \") {","+ e.preventDefault();","+ startGame();","+ }","+ return;","+ }","+","+ if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {","+ togglePause();","+ return;","+ }","+","+ if (paused) return;","+","+ switch (e.key) {","+ case \"ArrowLeft\":","+ e.preventDefault();","+ if (!dasActive) movePiece(-1, 0);","+ dasKey = \"left\";","+ dasTimer = 0;","+ dasActive = false;","+ break;","+ case \"ArrowRight\":","+ e.preventDefault();","+ if (!dasActive) movePiece(1, 0);","+ dasKey = \"right\";","+ dasTimer = 0;","+ dasActive = false;","+ break;","+ case \"ArrowDown\":","+ e.preventDefault();","+ if (moveDown()) {","+ score += 1;","+ updateStats();","+ }","+ break;","+ case \"ArrowUp\":","+ case \"x\":","+ case \"X\":","+ e.preventDefault();","+ rotatePiece(1);","+ break;","+ case \"z\":","+ case \"Z\":","+ e.preventDefault();","+ rotatePiece(-1);","+ break;","+ case \" \":","+ e.preventDefault();","+ hardDrop();","+ break;","+ case \"c\":","+ case \"C\":","+ case \"Shift\":","+ e.preventDefault();","+ holdPiece();","+ break;","+ }","+","+ render();","+}","+","+function handleKeyUp(e: KeyboardEvent): void {","+ if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;","+ if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;","+}","+","+function updateDAS(delta: number): void {","+ if (dasKey === null) return;","+ dasTimer += delta;","+ if (!dasActive && dasTimer >= DAS_DELAY) {","+ dasActive = true;","+ dasTimer = 0;","+ }","+ if (dasActive && dasTimer >= DAS_RATE) {","+ dasTimer -= DAS_RATE;","+ if (dasKey === \"left\") movePiece(-1, 0);","+ else if (dasKey === \"right\") movePiece(1, 0);","+ render();","+ }","+}","+","+// ----- Pause -----","+","+function togglePause(): void {","+ paused = !paused;","+ const overlay = document.getElementById(\"pause-overlay\")!;","+ if (paused) {","+ overlay.classList.remove(\"hidden\");","+ } else {","+ overlay.classList.add(\"hidden\");","+ lastTime = 0;","+ animFrameId = requestAnimationFrame(gameLoop);","+ }","+}","+","+// ----- Overlay Management -----","+","+function showOverlay(which: \"start\" | \"gameover\"): void {","+ const startOverlay = document.getElementById(\"start-overlay\")!;","+ const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;","+","+ startOverlay.classList.add(\"hidden\");","+ gameoverOverlay.classList.add(\"hidden\");","+","+ if (which === \"start\") {","+ startOverlay.classList.remove(\"hidden\");","+ } else {","+ document.getElementById(\"final-score\")!.textContent = score.toLocaleString();","+ gameoverOverlay.classList.remove(\"hidden\");","+ }","+}","+","+function hideAllOverlays(): void {","+ document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");","+ document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");","+ document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");","+}","+","+// ----- Start / Restart -----","+","+function startGame(): void {","+ hideAllOverlays();","+ initGame();","+ started = true;","+ lastTime = 0;","+ dropTimer = 0;","+ updateStats();","+ render();","+ animFrameId = requestAnimationFrame(gameLoop);","+}","+","+// ----- Boot -----","+","+function boot(): void {","+ const boardEl = buildBoardDOM();","+ cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];","+ nextPreviewCells = Array.from(","+ (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")","+ ) as HTMLDivElement[];","+ holdPreviewCells = Array.from(","+ (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")","+ ) as HTMLDivElement[];","+","+ document.addEventListener(\"keydown\", handleKeyDown);","+ document.addEventListener(\"keyup\", handleKeyUp);","+","+ initBoard();","+ showOverlay(\"start\");","+}","+","+document.addEventListener(\"DOMContentLoaded\", boot);"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_20260407095838a266570e1018456c","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now the HTML file:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"5a510781-ddbf-45bc-b3ce-3b90136d14c9"} +{"type":"assistant","message":{"id":"msg_20260407095838a266570e1018456c","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_347100283e3041be8a20ab25","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/index.html","old_string":"","new_string":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Tetris</title>\n <link rel=\"stylesheet\" href=\"styles.css\">\n</head>\n<body>\n <div class=\"game-wrapper\">\n <!-- Left Panel -->\n <div class=\"side-panel\">\n <div class=\"panel-box\">\n <h3>Hold</h3>\n <div id=\"hold-preview\" class=\"preview-grid\"></div>\n </div>\n <div class=\"panel-box controls-box\">\n <h3>Controls</h3>\n <div><kbd>←</kbd> <kbd>→</kbd> Move</div>\n <div><kbd>↑</kbd> / <kbd>X</kbd> Rotate CW</div>\n <div><kbd>Z</kbd> Rotate CCW</div>\n <div><kbd>↓</kbd> Soft drop</div>\n <div><kbd>Space</kbd> Hard drop</div>\n <div><kbd>C</kbd> / <kbd>Shift</kbd> Hold</div>\n <div><kbd>P</kbd> / <kbd>Esc</kbd> Pause</div>\n </div>\n </div>\n\n <!-- Board -->\n <div class=\"board-container\">\n <div class=\"board-frame\">\n <div id=\"board\" class=\"board\"></div>\n </div>\n\n <!-- Start Overlay -->\n <div id=\"start-overlay\" class=\"overlay\">\n <h1>Tetris</h1>\n <div class=\"subtitle\">DOM-rendered · TypeScript</div>\n <div class=\"prompt\">Press Enter or Space to start</div>\n </div>\n\n <!-- Pause Overlay -->\n <div id=\"pause-overlay\" class=\"overlay hidden\">\n <h1>Paused</h1>\n <div class=\"prompt\">Press P or Esc to resume</div>\n </div>\n\n <!-- Game Over Overlay -->\n <div id=\"gameover-overlay\" class=\"overlay hidden\">\n <h2>Game Over</h2>\n <div class=\"subtitle\">Score: <span id=\"final-score\">0</span></div>\n <div class=\"prompt\">Press Enter to play again</div>\n </div>\n </div>\n\n <!-- Right Panel -->\n <div class=\"side-panel\">\n <div class=\"panel-box\">\n <h3>Next</h3>\n <div id=\"next-preview\" class=\"preview-grid\"></div>\n </div>\n <div class=\"panel-box\">\n <h3>Score</h3>\n <div id=\"score-value\" class=\"value\">0</div>\n </div>\n <div class=\"panel-box\">\n <h3>Level</h3>\n <div id=\"level-value\" class=\"value\">1</div>\n </div>\n <div class=\"panel-box\">\n <h3>Lines</h3>\n <div id=\"lines-value\" class=\"value\">0</div>\n </div>\n </div>\n </div>\n\n <script src=\"tetris.js\"></script>\n</body>\n</html>\n"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"d9289722-a6dc-4f3e-8a62-0deb52979a1f"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_347100283e3041be8a20ab25","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/index.html has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"990333cd-8439-4a11-9aeb-42b9c8437b88","timestamp":"2026-04-07T01:59:06.671Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/index.html","oldString":"","newString":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Tetris</title>\n <link rel=\"stylesheet\" href=\"styles.css\">\n</head>\n<body>\n <div class=\"game-wrapper\">\n <!-- Left Panel -->\n <div class=\"side-panel\">\n <div class=\"panel-box\">\n <h3>Hold</h3>\n <div id=\"hold-preview\" class=\"preview-grid\"></div>\n </div>\n <div class=\"panel-box controls-box\">\n <h3>Controls</h3>\n <div><kbd>←</kbd> <kbd>→</kbd> Move</div>\n <div><kbd>↑</kbd> / <kbd>X</kbd> Rotate CW</div>\n <div><kbd>Z</kbd> Rotate CCW</div>\n <div><kbd>↓</kbd> Soft drop</div>\n <div><kbd>Space</kbd> Hard drop</div>\n <div><kbd>C</kbd> / <kbd>Shift</kbd> Hold</div>\n <div><kbd>P</kbd> / <kbd>Esc</kbd> Pause</div>\n </div>\n </div>\n\n <!-- Board -->\n <div class=\"board-container\">\n <div class=\"board-frame\">\n <div id=\"board\" class=\"board\"></div>\n </div>\n\n <!-- Start Overlay -->\n <div id=\"start-overlay\" class=\"overlay\">\n <h1>Tetris</h1>\n <div class=\"subtitle\">DOM-rendered · TypeScript</div>\n <div class=\"prompt\">Press Enter or Space to start</div>\n </div>\n\n <!-- Pause Overlay -->\n <div id=\"pause-overlay\" class=\"overlay hidden\">\n <h1>Paused</h1>\n <div class=\"prompt\">Press P or Esc to resume</div>\n </div>\n\n <!-- Game Over Overlay -->\n <div id=\"gameover-overlay\" class=\"overlay hidden\">\n <h2>Game Over</h2>\n <div class=\"subtitle\">Score: <span id=\"final-score\">0</span></div>\n <div class=\"prompt\">Press Enter to play again</div>\n </div>\n </div>\n\n <!-- Right Panel -->\n <div class=\"side-panel\">\n <div class=\"panel-box\">\n <h3>Next</h3>\n <div id=\"next-preview\" class=\"preview-grid\"></div>\n </div>\n <div class=\"panel-box\">\n <h3>Score</h3>\n <div id=\"score-value\" class=\"value\">0</div>\n </div>\n <div class=\"panel-box\">\n <h3>Level</h3>\n <div id=\"level-value\" class=\"value\">1</div>\n </div>\n <div class=\"panel-box\">\n <h3>Lines</h3>\n <div id=\"lines-value\" class=\"value\">0</div>\n </div>\n </div>\n </div>\n\n <script src=\"tetris.js\"></script>\n</body>\n</html>\n","originalFile":"","structuredPatch":[{"oldStart":1,"oldLines":0,"newStart":1,"newLines":79,"lines":["+<!DOCTYPE html>","+<html lang=\"en\">","+<head>","+ <meta charset=\"UTF-8\">","+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">","+ <title>Tetris</title>","+ <link rel=\"stylesheet\" href=\"styles.css\">","+</head>","+<body>","+ <div class=\"game-wrapper\">","+ <!-- Left Panel -->","+ <div class=\"side-panel\">","+ <div class=\"panel-box\">","+ <h3>Hold</h3>","+ <div id=\"hold-preview\" class=\"preview-grid\"></div>","+ </div>","+ <div class=\"panel-box controls-box\">","+ <h3>Controls</h3>","+ <div><kbd>←</kbd> <kbd>→</kbd> Move</div>","+ <div><kbd>↑</kbd> / <kbd>X</kbd> Rotate CW</div>","+ <div><kbd>Z</kbd> Rotate CCW</div>","+ <div><kbd>↓</kbd> Soft drop</div>","+ <div><kbd>Space</kbd> Hard drop</div>","+ <div><kbd>C</kbd> / <kbd>Shift</kbd> Hold</div>","+ <div><kbd>P</kbd> / <kbd>Esc</kbd> Pause</div>","+ </div>","+ </div>","+","+ <!-- Board -->","+ <div class=\"board-container\">","+ <div class=\"board-frame\">","+ <div id=\"board\" class=\"board\"></div>","+ </div>","+","+ <!-- Start Overlay -->","+ <div id=\"start-overlay\" class=\"overlay\">","+ <h1>Tetris</h1>","+ <div class=\"subtitle\">DOM-rendered · TypeScript</div>","+ <div class=\"prompt\">Press Enter or Space to start</div>","+ </div>","+","+ <!-- Pause Overlay -->","+ <div id=\"pause-overlay\" class=\"overlay hidden\">","+ <h1>Paused</h1>","+ <div class=\"prompt\">Press P or Esc to resume</div>","+ </div>","+","+ <!-- Game Over Overlay -->","+ <div id=\"gameover-overlay\" class=\"overlay hidden\">","+ <h2>Game Over</h2>","+ <div class=\"subtitle\">Score: <span id=\"final-score\">0</span></div>","+ <div class=\"prompt\">Press Enter to play again</div>","+ </div>","+ </div>","+","+ <!-- Right Panel -->","+ <div class=\"side-panel\">","+ <div class=\"panel-box\">","+ <h3>Next</h3>","+ <div id=\"next-preview\" class=\"preview-grid\"></div>","+ </div>","+ <div class=\"panel-box\">","+ <h3>Score</h3>","+ <div id=\"score-value\" class=\"value\">0</div>","+ </div>","+ <div class=\"panel-box\">","+ <h3>Level</h3>","+ <div id=\"level-value\" class=\"value\">1</div>","+ </div>","+ <div class=\"panel-box\">","+ <h3>Lines</h3>","+ <div id=\"lines-value\" class=\"value\">0</div>","+ </div>","+ </div>","+ </div>","+","+ <script src=\"tetris.js\"></script>","+</body>","+</html>"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_2026040709590817a012d325894b25","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now let me compile the TypeScript:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"08d7ea76-8a1d-4d76-b4a8-fbbe5f119306"} +{"type":"assistant","message":{"id":"msg_2026040709590817a012d325894b25","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_c9cd692e70304a0998bb6137","name":"Bash","input":{"command":"tsc --project tsconfig.json 2>&1","description":"Compile TypeScript to JavaScript"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"4d46aad6-35a2-4e1a-9347-9129648d7a35"} +{"type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"Exit code 2\nnode_modules/@types/node/http.d.ts(2176,36): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/http.d.ts(2180,37): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/http.d.ts(2184,39): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/web-globals/fetch.d.ts(3,25): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/worker_threads.d.ts(71,34): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\n[rerun: b4]","is_error":true,"tool_use_id":"call_c9cd692e70304a0998bb6137"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"623083a3-89e8-4a68-84ce-39586e8b087a","timestamp":"2026-04-07T01:59:12.764Z","tool_use_result":"Error: Exit code 2\nnode_modules/@types/node/http.d.ts(2176,36): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/http.d.ts(2180,37): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/http.d.ts(2184,39): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/web-globals/fetch.d.ts(3,25): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\nnode_modules/@types/node/worker_threads.d.ts(71,34): error TS2792: Cannot find module 'undici-types'. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?\n[rerun: b4]"} +{"type":"assistant","message":{"id":"msg_20260407095914478cc0a5648a4c97","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The issue is that node_modules has @types/node which conflicts. Let me update the tsconfig to skip lib check or exclude node_modules.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"d974c4b6-8232-45c1-a575-a49446bdc6c2"} +{"type":"assistant","message":{"id":"msg_20260407095914478cc0a5648a4c97","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Need to exclude node_modules from compilation:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"87ff22b5-c9f7-4361-a77d-14e7b710b5fe"} +{"type":"assistant","message":{"id":"msg_20260407095914478cc0a5648a4c97","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_735acd2a31554772aec61b93","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/tsconfig.json","old_string":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outDir\": \"src\",\n \"sourceMap\": false,\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"forceConsistentCasingInFileNames\": true\n },\n \"files\": [\"src/tetris.ts\"]\n}","new_string":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outDir\": \"src\",\n \"sourceMap\": false,\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src/tetris.ts\"],\n \"exclude\": [\"node_modules\"]\n}"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"a511946c-28b5-4fda-897d-8386dcf4f79b"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_735acd2a31554772aec61b93","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/tsconfig.json has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"4738d476-5378-4252-8e56-69d805c24ea2","timestamp":"2026-04-07T01:59:26.601Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/tsconfig.json","oldString":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outDir\": \"src\",\n \"sourceMap\": false,\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"forceConsistentCasingInFileNames\": true\n },\n \"files\": [\"src/tetris.ts\"]\n}","newString":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outDir\": \"src\",\n \"sourceMap\": false,\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src/tetris.ts\"],\n \"exclude\": [\"node_modules\"]\n}","originalFile":"{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outDir\": \"src\",\n \"sourceMap\": false,\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"forceConsistentCasingInFileNames\": true\n },\n \"files\": [\"src/tetris.ts\"]\n}","structuredPatch":[{"oldStart":7,"oldLines":7,"newStart":7,"newLines":9,"lines":[" \"strict\": true,"," \"noImplicitAny\": true,"," \"removeComments\": true,","- \"forceConsistentCasingInFileNames\": true","+ \"forceConsistentCasingInFileNames\": true,","+ \"skipLibCheck\": true"," },","- \"files\": [\"src/tetris.ts\"]","+ \"include\": [\"src/tetris.ts\"],","+ \"exclude\": [\"node_modules\"]"," }","\\ No newline at end of file"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_202604070959272bf6413078004f05","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7222430267fd4b769440f282","name":"Bash","input":{"command":"tsc --project tsconfig.json 2>&1","description":"Compile TypeScript with skipLibCheck"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"0feb6b71-fa61-4888-9187-f0f4100cf3ed"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7222430267fd4b769440f282","type":"tool_result","content":"[rerun: b5]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"af3bfc7b-4b24-4706-85c1-6cd93932ea98","timestamp":"2026-04-07T01:59:34.466Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604070959352ec161e6d04547d1","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The compilation succeeded. Let me verify the output files exist.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"c6d626eb-a3a5-4aec-bf2d-14dd18b076ff"} +{"type":"assistant","message":{"id":"msg_202604070959352ec161e6d04547d1","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Compiled successfully. Let me verify everything is in place:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"91ccb89b-c085-454e-962a-db8ec302f009"} +{"type":"assistant","message":{"id":"msg_202604070959352ec161e6d04547d1","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_14f93b4348ed4d6a95302952","name":"Bash","input":{"command":"ls -la /tmp/loop-bench-4v9zg5jy/src/","description":"Verify all output files exist"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"b582c39e-b40e-4a5a-a70b-b59ac5097c68"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_14f93b4348ed4d6a95302952","type":"tool_result","content":"total 60\ndrwxr-xr-x 2 root root 4096 Apr 7 03:59 .\ndrwx------ 6 root root 4096 Apr 7 03:59 ..\n-rw-r--r-- 1 root root 2331 Apr 7 03:59 index.html\n-rw-r--r-- 1 root root 4998 Apr 7 03:56 styles.css\n-rw-r--r-- 1 root root 17744 Apr 7 03:59 tetris.js\n-rw-r--r-- 1 root root 18500 Apr 7 03:58 tetris.ts\n[rerun: b6]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"f3c95ea6-2d5c-4641-b7ac-c3aa0e91e3de","timestamp":"2026-04-07T01:59:43.474Z","tool_use_result":{"stdout":"total 60\ndrwxr-xr-x 2 root root 4096 Apr 7 03:59 .\ndrwx------ 6 root root 4096 Apr 7 03:59 ..\n-rw-r--r-- 1 root root 2331 Apr 7 03:59 index.html\n-rw-r--r-- 1 root root 4998 Apr 7 03:56 styles.css\n-rw-r--r-- 1 root root 17744 Apr 7 03:59 tetris.js\n-rw-r--r-- 1 root root 18500 Apr 7 03:58 tetris.ts","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604070959445f5d2200296e4803","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"All files are present. Let me quickly verify the compiled JS has the DOMContentLoaded listener so the game bootstraps properly:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"de5c0843-5e04-4c0a-a083-fa9ec2c70817"} +{"type":"assistant","message":{"id":"msg_202604070959445f5d2200296e4803","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_4351ffcc0fe94d3281baa2d2","name":"Bash","input":{"command":"tail -5 /tmp/loop-bench-4v9zg5jy/src/tetris.js","description":"Verify JS ends with DOMContentLoaded listener"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"f1e4da04-2ba2-47f7-b92b-f4c824d733c5"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_4351ffcc0fe94d3281baa2d2","type":"tool_result","content":" document.addEventListener(\"keyup\", handleKeyUp);\n initBoard();\n showOverlay(\"start\");\n}\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n[rerun: b7]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"bf3365ed-5a2f-4d15-8c4b-be41ffb51839","timestamp":"2026-04-07T01:59:50.362Z","tool_use_result":{"stdout":" document.addEventListener(\"keyup\", handleKeyUp);\n initBoard();\n showOverlay(\"start\");\n}\ndocument.addEventListener(\"DOMContentLoaded\", boot);","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407095952bc88a1b2cf9f4796","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Let me also verify there's nothing obviously broken by checking for any runtime issues — I notice the DAS (auto-repeat) timer is accumulated but the `updateDAS` function is defined but never called from the game loop. Let me fix that:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"68c279a1-8f60-49eb-9f67-85c0174ef097"} +{"type":"assistant","message":{"id":"msg_20260407095952bc88a1b2cf9f4796","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7abda7823aaf4c84bc957503","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","offset":366,"limit":30}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"2d856672-0b0e-4dc2-a53e-41831f1fbeb0"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7abda7823aaf4c84bc957503","type":"tool_result","content":"366\t if (isValid(currentPiece.shape, newPos)) {\n367\t currentPiece.pos = newPos;\n368\t return true;\n369\t }\n370\t return false;\n371\t}\n372\t\n373\tfunction moveDown(): boolean {\n374\t if (!currentPiece) return false;\n375\t if (movePiece(0, 1)) return true;\n376\t lockPiece();\n377\t return false;\n378\t}\n379\t\n380\t// ----- Rotation with Wall Kicks (SRS) -----\n381\t\n382\tfunction rotatePiece(dir: 1 | -1): void {\n383\t if (!currentPiece) return;\n384\t const oldRot = currentPiece.rotation;\n385\t const newRot = (oldRot + dir + 4) % 4;\n386\t const newShape = SHAPES[currentPiece.type][newRot];\n387\t\n388\t const kickKey = `${oldRot}>${newRot}`;\n389\t const kicks = currentPiece.type === \"I\"\n390\t ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n391\t : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n392\t\n393\t for (const [kx, ky] of kicks) {\n394\t const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n395\t if (isValid(newShape, testPos)) {"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"e831e9dd-9f4c-4af8-805e-8ae7019cfcbb","timestamp":"2026-04-07T02:00:02.380Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":" if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {","numLines":30,"startLine":366,"totalLines":804}}} +{"type":"assistant","message":{"id":"msg_20260407100003c4040c2be3f74528","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_946d0c7e79f64442b4978ed8","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":25,"offset":470}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"02aacd36-6f3c-4621-bf7e-a5b4d5d0e63b"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_946d0c7e79f64442b4978ed8","type":"tool_result","content":"470\t updateStats();\n471\t spawnPiece();\n472\t });\n473\t } else {\n474\t combo = -1;\n475\t updateStats();\n476\t spawnPiece();\n477\t }\n478\t}\n479\t\n480\tfunction animateClear(rows: number[], callback: () => void): void {\n481\t // Mark cells as clearing\n482\t for (const r of rows) {\n483\t for (let c = 0; c < COLS; c++) {\n484\t const boardRow = r - HIDDEN_ROWS;\n485\t if (boardRow >= 0 && boardRow < ROWS) {\n486\t const idx = boardRow * COLS + c;\n487\t cells[idx].classList.add(\"clearing\");\n488\t }\n489\t }\n490\t }\n491\t setTimeout(callback, 400);\n492\t}\n493\t\n494\tfunction removeLines(rows: number[]): void {"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"ebd14b57-4ea3-4006-8bc5-31863b2a0037","timestamp":"2026-04-07T02:00:14.632Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":" updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {","numLines":25,"startLine":470,"totalLines":804}}} +{"type":"assistant","message":{"id":"msg_20260407100016351551cef3ad4d82","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_fb4f39569e6e44d0a1f800ea","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":25,"offset":560}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"d96aaa5b-ab80-4619-8c63-3791457607ad"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_fb4f39569e6e44d0a1f800ea","type":"tool_result","content":"560\t const bx = pos.x + c;\n561\t const boardRow = by - HIDDEN_ROWS;\n562\t if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n563\t const idx = boardRow * COLS + bx;\n564\t cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n565\t if (cls === \"ghost\") {\n566\t cells[idx].className = \"cell ghost\";\n567\t }\n568\t }\n569\t }\n570\t }\n571\t}\n572\t\n573\tfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n574\t const shape = SHAPES[type][0];\n575\t // Center the shape in the 4×4 preview\n576\t const offR = Math.floor((4 - shape.length) / 2);\n577\t const offC = Math.floor((4 - shape[0].length) / 2);\n578\t for (let r = 0; r < 4; r++) {\n579\t for (let c = 0; c < 4; c++) {\n580\t const idx = r * 4 + c;\n581\t previewCells[idx].className = \"preview-cell\";\n582\t const sr = r - offR;\n583\t const sc = c - offC;\n584\t if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"1ba64c35-9eaf-4385-adcc-d91a0c13b440","timestamp":"2026-04-07T02:00:23.055Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":" const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {","numLines":25,"startLine":560,"totalLines":804}}} +{"type":"assistant","message":{"id":"msg_20260407100024469ae359b0a1490a","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now let me find the game loop and add the DAS update call:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"8d9c866e-bf24-433e-b7ff-2193916ee902"} +{"type":"assistant","message":{"id":"msg_20260407100024469ae359b0a1490a","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7467c67efe3342779e5bac41","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":25,"offset":610}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"ac781c3d-52fc-43c1-8625-8a30e6512e46"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7467c67efe3342779e5bac41","type":"tool_result","content":"610\t // Speed curve (milliseconds per drop), gets faster each level\n611\t // Based roughly on NES Tetris speed curve\n612\t const speeds = [\n613\t 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n614\t 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n615\t ];\n616\t if (level < speeds.length) return speeds[level];\n617\t return 5;\n618\t}\n619\t\n620\t// ----- Game Loop -----\n621\t\n622\tfunction gameLoop(timestamp: number): void {\n623\t if (gameOver || paused || !started) return;\n624\t\n625\t if (lastTime === 0) lastTime = timestamp;\n626\t const delta = timestamp - lastTime;\n627\t lastTime = timestamp;\n628\t\n629\t dropTimer += delta;\n630\t const interval = getDropInterval();\n631\t\n632\t if (dropTimer >= interval) {\n633\t dropTimer -= interval;\n634\t moveDown();"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"d5682139-0b14-45dc-85df-208d5b430105","timestamp":"2026-04-07T02:00:30.918Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":" // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();","numLines":25,"startLine":610,"totalLines":804}}} +{"type":"assistant","message":{"id":"msg_202604071000325224e27558544517","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_eb4d688be6f14bcea168d349","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","old_string":" dropTimer += delta;\n const interval = getDropInterval();","new_string":" updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"83ef0b63-f8da-473b-bb1f-9ecdba76191f"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_eb4d688be6f14bcea168d349","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/tetris.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"e5c4d7d9-b984-4fd8-b029-1997ba972ced","timestamp":"2026-04-07T02:00:41.477Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","oldString":" dropTimer += delta;\n const interval = getDropInterval();","newString":" updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();","originalFile":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","structuredPatch":[{"oldStart":626,"oldLines":6,"newStart":626,"newLines":8,"lines":[" const delta = timestamp - lastTime;"," lastTime = timestamp;"," ","+ updateDAS(delta);","+"," dropTimer += delta;"," const interval = getDropInterval();"," "]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_20260407100042f658372348064ea4","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now recompile:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"4cf819ca-f686-42e3-8b77-7f907b289400"} +{"type":"assistant","message":{"id":"msg_20260407100042f658372348064ea4","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_e3aa8728c3774269957ecfaf","name":"Bash","input":{"command":"tsc --project tsconfig.json 2>&1","description":"Recompile TypeScript after DAS fix"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"7de8eeb6-bcef-4643-9ee2-4ecb608f234f"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_e3aa8728c3774269957ecfaf","type":"tool_result","content":"[rerun: b8]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"cfa4f686-6163-4de2-b655-031bca803bac","timestamp":"2026-04-07T02:00:47.282Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407100048bb54964e3cb24ad2","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Good. Let me also verify the game handles the animation timing correctly — the `spawnPiece` is called inside the `animateClear` callback, and during that 400ms the game loop continues. I should make sure the game loop doesn't drop during line clear animation. Let me add a flag for that:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"c23b5623-d546-4dda-b873-0927c63f7424"} +{"type":"assistant","message":{"id":"msg_20260407100048bb54964e3cb24ad2","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_9d20af6310754805ada82810","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":10,"offset":113}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"012546ed-d8f8-4907-85f8-e5e3ea6efc6b"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_9d20af6310754805ada82810","type":"tool_result","content":"113\t [0, 1, 0],\n114\t ],\n115\t ],\n116\t Z: [\n117\t [\n118\t [1, 1, 0],\n119\t [0, 1, 1],\n120\t [0, 0, 0],\n121\t ],\n122\t ["}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"46cec3d0-dc2c-47df-a9e5-2c966d2871dc","timestamp":"2026-04-07T02:00:57.211Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":" [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [","numLines":10,"startLine":113,"totalLines":806}}} +{"type":"assistant","message":{"id":"msg_202604071000588dc24ccd87f24a57","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_a420282e76644d71a98e49f0","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":15,"offset":167}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"4da35809-f455-4f08-a062-3703b16ff2dd"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_a420282e76644d71a98e49f0","type":"tool_result","content":"167\t [0, 1, 0],\n168\t [0, 1, 0],\n169\t [0, 1, 1],\n170\t ],\n171\t [\n172\t [0, 0, 0],\n173\t [1, 1, 1],\n174\t [1, 0, 0],\n175\t ],\n176\t [\n177\t [1, 1, 0],\n178\t [0, 1, 0],\n179\t [0, 1, 0],\n180\t ],\n181\t ],"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"9229d039-9530-42ad-a231-66ff2d0c6dbb","timestamp":"2026-04-07T02:01:02.967Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":" [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],","numLines":15,"startLine":167,"totalLines":806}}} +{"type":"assistant","message":{"id":"msg_202604071001040087e5ba746a439e","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_b59af4b66117435bb44b2926","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":20,"offset":220}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"b939e689-9333-4cac-b650-2457012a628f"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_b59af4b66117435bb44b2926","type":"tool_result","content":"220\t\n221\tfunction createCell(className: string): HTMLDivElement {\n222\t const div = document.createElement(\"div\");\n223\t div.className = className;\n224\t return div;\n225\t}\n226\t\n227\tfunction buildBoardDOM(): HTMLDivElement {\n228\t const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n229\t boardEl.innerHTML = \"\";\n230\t for (let r = 0; r < ROWS; r++) {\n231\t for (let c = 0; c < COLS; c++) {\n232\t boardEl.appendChild(createCell(\"cell\"));\n233\t }\n234\t }\n235\t return boardEl;\n236\t}\n237\t\n238\tfunction buildPreviewDOM(id: string): HTMLDivElement {\n239\t const el = document.getElementById(id) as HTMLDivElement;"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"d3d95734-5d53-4398-8924-b4c462fb2bde","timestamp":"2026-04-07T02:01:09.967Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":"\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;","numLines":20,"startLine":220,"totalLines":806}}} +{"type":"assistant","message":{"id":"msg_20260407100111352ec1f18a6f4e52","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Let me add a `clearing` flag to prevent the game loop from acting during line clear animations:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"79d37742-531f-418b-9be7-ebb6b7c340c0"} +{"type":"assistant","message":{"id":"msg_20260407100111352ec1f18a6f4e52","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_d534f1917d3741e6894ac453","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":20,"offset":253}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"e1815ab5-e330-4b19-8e20-3a26581452d7"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_d534f1917d3741e6894ac453","type":"tool_result","content":"253\tlet nextPreviewCells: HTMLDivElement[];\n254\tlet holdPreviewCells: HTMLDivElement[];\n255\t\n256\tlet currentPiece: ActivePiece | null;\n257\tlet nextType: PieceType;\n258\tlet holdType: PieceType | null;\n259\tlet canHold: boolean;\n260\t\n261\tlet score: number;\n262\tlet level: number;\n263\tlet linesCleared: number;\n264\tlet combo: number;\n265\t\n266\tlet gameOver: boolean;\n267\tlet paused: boolean;\n268\tlet started: boolean;\n269\t\n270\tlet dropTimer: number;\n271\tlet lastTime: number;\n272\tlet animFrameId: number;"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"1b0cc0af-de1d-4367-9f12-0545ab22020f","timestamp":"2026-04-07T02:01:17.377Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":"let nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;","numLines":20,"startLine":253,"totalLines":806}}} +{"type":"assistant","message":{"id":"msg_20260407100119042d1a0aea76436e","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_f3da5f09ff2643479cf8e146","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","old_string":"let gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;","new_string":"let gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\nlet clearing: boolean;"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"73a36d50-0d88-42e4-a816-419b3d6bf2a7"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_f3da5f09ff2643479cf8e146","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/tetris.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"5dcdf489-b535-4859-8d90-5f99296dc1bc","timestamp":"2026-04-07T02:01:24.128Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","oldString":"let gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;","newString":"let gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\nlet clearing: boolean;","originalFile":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","structuredPatch":[{"oldStart":266,"oldLines":6,"newStart":266,"newLines":7,"lines":[" let gameOver: boolean;"," let paused: boolean;"," let started: boolean;","+let clearing: boolean;"," "," let dropTimer: number;"," let lastTime: number;"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_20260407100125aaa89ddfa1814b77","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now add `clearing = false` in `initGame`:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"f1f15eb1-10c0-47d3-89ca-9bd606a29ef4"} +{"type":"assistant","message":{"id":"msg_20260407100125aaa89ddfa1814b77","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_881a8df0909d4e0fb62042b6","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":20,"offset":279}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"c87eef11-57a8-47db-ac9b-aafc71f89b07"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_881a8df0909d4e0fb62042b6","type":"tool_result","content":"279\t\n280\tfunction initBoard(): void {\n281\t board = [];\n282\t for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n283\t board.push(new Array<PieceType | null>(COLS).fill(null));\n284\t }\n285\t}\n286\t\n287\tfunction initGame(): void {\n288\t initBoard();\n289\t score = 0;\n290\t level = 0;\n291\t linesCleared = 0;\n292\t combo = -1;\n293\t gameOver = false;\n294\t paused = false;\n295\t holdType = null;\n296\t canHold = true;\n297\t bag = [];\n298\t nextType = pullFromBag();"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"9326f9e7-32d9-4a28-8762-2dc9f61188c7","timestamp":"2026-04-07T02:01:30.170Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":"\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();","numLines":20,"startLine":279,"totalLines":807}}} +{"type":"assistant","message":{"id":"msg_202604071001319fb119e4c5e64295","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_27ede151f71840f68b7bf7be","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","old_string":" gameOver = false;\n paused = false;\n holdType = null;","new_string":" gameOver = false;\n paused = false;\n clearing = false;\n holdType = null;"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"0c8278d0-5723-4989-9231-ef2c40efd320"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_27ede151f71840f68b7bf7be","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/tetris.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"3d63cf4e-2bb6-4570-a571-329f387c42db","timestamp":"2026-04-07T02:01:36.028Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","oldString":" gameOver = false;\n paused = false;\n holdType = null;","newString":" gameOver = false;\n paused = false;\n clearing = false;\n holdType = null;","originalFile":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\nlet clearing: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","structuredPatch":[{"oldStart":292,"oldLines":6,"newStart":292,"newLines":7,"lines":[" combo = -1;"," gameOver = false;"," paused = false;","+ clearing = false;"," holdType = null;"," canHold = true;"," bag = [];"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_20260407100138dd534780035b4462","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now update `lockPiece` to set the `clearing` flag:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"59cb16da-6b64-4f25-8c47-9d5644888d85"} +{"type":"assistant","message":{"id":"msg_20260407100138dd534780035b4462","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_5bb1d7fe11ce48099f031e4c","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","limit":35,"offset":454}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"e931e7e3-04aa-4ecd-8fc3-382b579cddb6"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_5bb1d7fe11ce48099f031e4c","type":"tool_result","content":"454\t clearedRows.push(r);\n455\t }\n456\t }\n457\t\n458\t if (clearedRows.length > 0) {\n459\t combo++;\n460\t // Animate then remove\n461\t animateClear(clearedRows, () => {\n462\t removeLines(clearedRows);\n463\t const n = clearedRows.length;\n464\t const baseScore = LINE_SCORES[n] || 0;\n465\t score += baseScore * (level + 1);\n466\t // Combo bonus\n467\t if (combo > 0) {\n468\t score += 50 * combo * (level + 1);\n469\t }\n470\t linesCleared += n;\n471\t level = Math.floor(linesCleared / 10);\n472\t updateStats();\n473\t spawnPiece();\n474\t });\n475\t } else {\n476\t combo = -1;\n477\t updateStats();\n478\t spawnPiece();\n479\t }\n480\t}\n481\t\n482\tfunction animateClear(rows: number[], callback: () => void): void {\n483\t // Mark cells as clearing\n484\t for (const r of rows) {\n485\t for (let c = 0; c < COLS; c++) {\n486\t const boardRow = r - HIDDEN_ROWS;\n487\t if (boardRow >= 0 && boardRow < ROWS) {\n488\t const idx = boardRow * COLS + c;"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"ae6e4682-f1e2-4c56-af99-1c51a813bd80","timestamp":"2026-04-07T02:01:45.818Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":" clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;","numLines":35,"startLine":454,"totalLines":808}}} +{"type":"assistant","message":{"id":"msg_2026040710014743126efd37474f77","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_1568bbf38b37494d869f12a6","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","old_string":" if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });","new_string":" if (clearedRows.length > 0) {\n combo++;\n clearing = true;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n clearing = false;\n spawnPiece();\n });"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"f9f180c1-3b3c-4171-ad39-430ae378855a"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_1568bbf38b37494d869f12a6","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/tetris.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"aacb66da-2247-469c-96c7-7d7e90f23b3a","timestamp":"2026-04-07T02:02:02.142Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","oldString":" if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });","newString":" if (clearedRows.length > 0) {\n combo++;\n clearing = true;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n clearing = false;\n spawnPiece();\n });","originalFile":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\nlet clearing: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n clearing = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","structuredPatch":[{"oldStart":457,"oldLines":6,"newStart":457,"newLines":7,"lines":[" "," if (clearedRows.length > 0) {"," combo++;","+ clearing = true;"," // Animate then remove"," animateClear(clearedRows, () => {"," removeLines(clearedRows);"]},{"oldStart":470,"oldLines":6,"newStart":471,"newLines":7,"lines":[" linesCleared += n;"," level = Math.floor(linesCleared / 10);"," updateStats();","+ clearing = false;"," spawnPiece();"," });"," } else {"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_202604071002033bb2f6089964456e","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now update the game loop to respect the clearing flag:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"5c251303-2d8b-4390-90af-de1a8f9014eb"} +{"type":"assistant","message":{"id":"msg_202604071002033bb2f6089964456e","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_d19aa0c9b562422aa3c9a28f","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","old_string":"function gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;","new_string":"function gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (clearing) {\n animFrameId = requestAnimationFrame(gameLoop);\n return;\n }"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"50f8eb4b-eefb-4221-93d6-5c0625663e7b"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_d19aa0c9b562422aa3c9a28f","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/tetris.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"78d6a163-8d27-43da-a321-bd6c59eea4bb","timestamp":"2026-04-07T02:02:10.174Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","oldString":"function gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;","newString":"function gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (clearing) {\n animFrameId = requestAnimationFrame(gameLoop);\n return;\n }","originalFile":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\nlet clearing: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n clearing = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n clearing = true;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n clearing = false;\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","structuredPatch":[{"oldStart":626,"oldLines":6,"newStart":626,"newLines":11,"lines":[" function gameLoop(timestamp: number): void {"," if (gameOver || paused || !started) return;"," ","+ if (clearing) {","+ animFrameId = requestAnimationFrame(gameLoop);","+ return;","+ }","+"," if (lastTime === 0) lastTime = timestamp;"," const delta = timestamp - lastTime;"," lastTime = timestamp;"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_2026040710021195fb72dbca734a25","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Recompile:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"4f38879a-e435-47fc-b3f2-5925443c9d76"} +{"type":"assistant","message":{"id":"msg_2026040710021195fb72dbca734a25","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_e843b22cce294b3e86b94b8a","name":"Bash","input":{"command":"tsc --project tsconfig.json 2>&1","description":"Recompile after clearing flag addition"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"e697271e-e486-475a-9166-43b8f10de3ff"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_e843b22cce294b3e86b94b8a","type":"tool_result","content":"[rerun: b9]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"34fb7b78-9e24-4e60-9d59-088e70e53a0e","timestamp":"2026-04-07T02:02:16.377Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071002172fba443866cc4e26","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Let me do a final review of the complete TypeScript to make sure everything is solid:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"31cd409c-b35a-4d49-851e-db098a831d7b"} +{"type":"assistant","message":{"id":"msg_202604071002172fba443866cc4e26","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_bb1758dc5d734bea8e716942","name":"Read","input":{"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"8e041e76-8277-4ea6-8f4f-7a00159738a2"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_bb1758dc5d734bea8e716942","type":"tool_result","content":"1\t// ============================================================\n2\t// Tetris – DOM-rendered, TypeScript\n3\t// ============================================================\n4\t\n5\t// ----- Types & Constants -----\n6\t\n7\ttype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n8\t\n9\tinterface Position {\n10\t x: number;\n11\t y: number;\n12\t}\n13\t\n14\tinterface ActivePiece {\n15\t type: PieceType;\n16\t shape: number[][];\n17\t pos: Position;\n18\t rotation: number;\n19\t}\n20\t\n21\tconst COLS = 10;\n22\tconst ROWS = 20;\n23\tconst HIDDEN_ROWS = 2; // buffer rows above visible area\n24\t\n25\t// Shapes stored as 4×4 (or smaller) matrices per rotation state\n26\t// Using the SRS-like representations\n27\tconst SHAPES: Record<PieceType, number[][][]> = {\n28\t I: [\n29\t [\n30\t [0, 0, 0, 0],\n31\t [1, 1, 1, 1],\n32\t [0, 0, 0, 0],\n33\t [0, 0, 0, 0],\n34\t ],\n35\t [\n36\t [0, 0, 1, 0],\n37\t [0, 0, 1, 0],\n38\t [0, 0, 1, 0],\n39\t [0, 0, 1, 0],\n40\t ],\n41\t [\n42\t [0, 0, 0, 0],\n43\t [0, 0, 0, 0],\n44\t [1, 1, 1, 1],\n45\t [0, 0, 0, 0],\n46\t ],\n47\t [\n48\t [0, 1, 0, 0],\n49\t [0, 1, 0, 0],\n50\t [0, 1, 0, 0],\n51\t [0, 1, 0, 0],\n52\t ],\n53\t ],\n54\t O: [\n55\t [\n56\t [1, 1],\n57\t [1, 1],\n58\t ],\n59\t [\n60\t [1, 1],\n61\t [1, 1],\n62\t ],\n63\t [\n64\t [1, 1],\n65\t [1, 1],\n66\t ],\n67\t [\n68\t [1, 1],\n69\t [1, 1],\n70\t ],\n71\t ],\n72\t T: [\n73\t [\n74\t [0, 1, 0],\n75\t [1, 1, 1],\n76\t [0, 0, 0],\n77\t ],\n78\t [\n79\t [0, 1, 0],\n80\t [0, 1, 1],\n81\t [0, 1, 0],\n82\t ],\n83\t [\n84\t [0, 0, 0],\n85\t [1, 1, 1],\n86\t [0, 1, 0],\n87\t ],\n88\t [\n89\t [0, 1, 0],\n90\t [1, 1, 0],\n91\t [0, 1, 0],\n92\t ],\n93\t ],\n94\t S: [\n95\t [\n96\t [0, 1, 1],\n97\t [1, 1, 0],\n98\t [0, 0, 0],\n99\t ],\n100\t [\n101\t [0, 1, 0],\n102\t [0, 1, 1],\n103\t [0, 0, 1],\n104\t ],\n105\t [\n106\t [0, 0, 0],\n107\t [0, 1, 1],\n108\t [1, 1, 0],\n109\t ],\n110\t [\n111\t [1, 0, 0],\n112\t [1, 1, 0],\n113\t [0, 1, 0],\n114\t ],\n115\t ],\n116\t Z: [\n117\t [\n118\t [1, 1, 0],\n119\t [0, 1, 1],\n120\t [0, 0, 0],\n121\t ],\n122\t [\n123\t [0, 0, 1],\n124\t [0, 1, 1],\n125\t [0, 1, 0],\n126\t ],\n127\t [\n128\t [0, 0, 0],\n129\t [1, 1, 0],\n130\t [0, 1, 1],\n131\t ],\n132\t [\n133\t [0, 1, 0],\n134\t [1, 1, 0],\n135\t [1, 0, 0],\n136\t ],\n137\t ],\n138\t J: [\n139\t [\n140\t [1, 0, 0],\n141\t [1, 1, 1],\n142\t [0, 0, 0],\n143\t ],\n144\t [\n145\t [0, 1, 1],\n146\t [0, 1, 0],\n147\t [0, 1, 0],\n148\t ],\n149\t [\n150\t [0, 0, 0],\n151\t [1, 1, 1],\n152\t [0, 0, 1],\n153\t ],\n154\t [\n155\t [0, 1, 0],\n156\t [0, 1, 0],\n157\t [1, 1, 0],\n158\t ],\n159\t ],\n160\t L: [\n161\t [\n162\t [0, 0, 1],\n163\t [1, 1, 1],\n164\t [0, 0, 0],\n165\t ],\n166\t [\n167\t [0, 1, 0],\n168\t [0, 1, 0],\n169\t [0, 1, 1],\n170\t ],\n171\t [\n172\t [0, 0, 0],\n173\t [1, 1, 1],\n174\t [1, 0, 0],\n175\t ],\n176\t [\n177\t [1, 1, 0],\n178\t [0, 1, 0],\n179\t [0, 1, 0],\n180\t ],\n181\t ],\n182\t};\n183\t\n184\t// Wall kick data (SRS standard)\n185\t// For J, L, S, T, Z pieces:\n186\tconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n187\t \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n188\t \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n189\t \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n190\t \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n191\t \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n192\t \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n193\t \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n194\t \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n195\t};\n196\t\n197\t// For I piece:\n198\tconst WALL_KICKS_I: Record<string, [number, number][]> = {\n199\t \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n200\t \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n201\t \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n202\t \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n203\t \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n204\t \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n205\t \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n206\t \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n207\t};\n208\t\n209\t// Scoring table (NES-style)\n210\tconst LINE_SCORES: Record<number, number> = {\n211\t 1: 100,\n212\t 2: 300,\n213\t 3: 500,\n214\t 4: 800,\n215\t};\n216\t\n217\tconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n218\t\n219\t// ----- DOM Setup -----\n220\t\n221\tfunction createCell(className: string): HTMLDivElement {\n222\t const div = document.createElement(\"div\");\n223\t div.className = className;\n224\t return div;\n225\t}\n226\t\n227\tfunction buildBoardDOM(): HTMLDivElement {\n228\t const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n229\t boardEl.innerHTML = \"\";\n230\t for (let r = 0; r < ROWS; r++) {\n231\t for (let c = 0; c < COLS; c++) {\n232\t boardEl.appendChild(createCell(\"cell\"));\n233\t }\n234\t }\n235\t return boardEl;\n236\t}\n237\t\n238\tfunction buildPreviewDOM(id: string): HTMLDivElement {\n239\t const el = document.getElementById(id) as HTMLDivElement;\n240\t el.innerHTML = \"\";\n241\t for (let r = 0; r < 4; r++) {\n242\t for (let c = 0; c < 4; c++) {\n243\t el.appendChild(createCell(\"preview-cell\"));\n244\t }\n245\t }\n246\t return el;\n247\t}\n248\t\n249\t// ----- Game State -----\n250\t\n251\tlet board: (PieceType | null)[][];\n252\tlet cells: HTMLDivElement[];\n253\tlet nextPreviewCells: HTMLDivElement[];\n254\tlet holdPreviewCells: HTMLDivElement[];\n255\t\n256\tlet currentPiece: ActivePiece | null;\n257\tlet nextType: PieceType;\n258\tlet holdType: PieceType | null;\n259\tlet canHold: boolean;\n260\t\n261\tlet score: number;\n262\tlet level: number;\n263\tlet linesCleared: number;\n264\tlet combo: number;\n265\t\n266\tlet gameOver: boolean;\n267\tlet paused: boolean;\n268\tlet started: boolean;\n269\tlet clearing: boolean;\n270\t\n271\tlet dropTimer: number;\n272\tlet lastTime: number;\n273\tlet animFrameId: number;\n274\t\n275\t// Bag randomizer for fair piece distribution\n276\tlet bag: PieceType[] = [];\n277\t\n278\t// ----- Initialization -----\n279\t\n280\tfunction initBoard(): void {\n281\t board = [];\n282\t for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n283\t board.push(new Array<PieceType | null>(COLS).fill(null));\n284\t }\n285\t}\n286\t\n287\tfunction initGame(): void {\n288\t initBoard();\n289\t score = 0;\n290\t level = 0;\n291\t linesCleared = 0;\n292\t combo = -1;\n293\t gameOver = false;\n294\t paused = false;\n295\t clearing = false;\n296\t holdType = null;\n297\t canHold = true;\n298\t bag = [];\n299\t nextType = pullFromBag();\n300\t currentPiece = null;\n301\t dropTimer = 0;\n302\t lastTime = 0;\n303\t spawnPiece();\n304\t}\n305\t\n306\t// ----- Bag Randomizer -----\n307\t\n308\tfunction fillBag(): void {\n309\t const b = [...PIECE_TYPES];\n310\t // Fisher-Yates shuffle\n311\t for (let i = b.length - 1; i > 0; i--) {\n312\t const j = Math.floor(Math.random() * (i + 1));\n313\t [b[i], b[j]] = [b[j], b[i]];\n314\t }\n315\t bag = b;\n316\t}\n317\t\n318\tfunction pullFromBag(): PieceType {\n319\t if (bag.length === 0) fillBag();\n320\t return bag.pop()!;\n321\t}\n322\t\n323\t// ----- Piece Spawning -----\n324\t\n325\tfunction spawnPiece(): void {\n326\t const type = nextType;\n327\t nextType = pullFromBag();\n328\t canHold = true;\n329\t\n330\t const shape = SHAPES[type][0];\n331\t // Spawn centered, in the hidden rows area\n332\t const x = Math.floor((COLS - shape[0].length) / 2);\n333\t const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n334\t\n335\t currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n336\t\n337\t if (!isValid(shape, currentPiece.pos)) {\n338\t // Game over – can't spawn\n339\t gameOver = true;\n340\t currentPiece = null;\n341\t showOverlay(\"gameover\");\n342\t }\n343\t\n344\t renderPreview(nextPreviewCells, nextType);\n345\t renderHoldPreview();\n346\t}\n347\t\n348\t// ----- Collision Detection -----\n349\t\n350\tfunction isValid(shape: number[][], pos: Position): boolean {\n351\t for (let r = 0; r < shape.length; r++) {\n352\t for (let c = 0; c < shape[r].length; c++) {\n353\t if (!shape[r][c]) continue;\n354\t const bx = pos.x + c;\n355\t const by = pos.y + r;\n356\t if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n357\t if (by >= 0 && board[by][bx] !== null) return false;\n358\t }\n359\t }\n360\t return true;\n361\t}\n362\t\n363\t// ----- Movement -----\n364\t\n365\tfunction movePiece(dx: number, dy: number): boolean {\n366\t if (!currentPiece) return false;\n367\t const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n368\t if (isValid(currentPiece.shape, newPos)) {\n369\t currentPiece.pos = newPos;\n370\t return true;\n371\t }\n372\t return false;\n373\t}\n374\t\n375\tfunction moveDown(): boolean {\n376\t if (!currentPiece) return false;\n377\t if (movePiece(0, 1)) return true;\n378\t lockPiece();\n379\t return false;\n380\t}\n381\t\n382\t// ----- Rotation with Wall Kicks (SRS) -----\n383\t\n384\tfunction rotatePiece(dir: 1 | -1): void {\n385\t if (!currentPiece) return;\n386\t const oldRot = currentPiece.rotation;\n387\t const newRot = (oldRot + dir + 4) % 4;\n388\t const newShape = SHAPES[currentPiece.type][newRot];\n389\t\n390\t const kickKey = `${oldRot}>${newRot}`;\n391\t const kicks = currentPiece.type === \"I\"\n392\t ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n393\t : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n394\t\n395\t for (const [kx, ky] of kicks) {\n396\t const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n397\t if (isValid(newShape, testPos)) {\n398\t currentPiece.shape = newShape;\n399\t currentPiece.pos = testPos;\n400\t currentPiece.rotation = newRot;\n401\t return;\n402\t }\n403\t }\n404\t // Rotation failed – no valid kick\n405\t}\n406\t\n407\t// ----- Hard Drop -----\n408\t\n409\tfunction hardDrop(): void {\n410\t if (!currentPiece) return;\n411\t let dropped = 0;\n412\t while (movePiece(0, 1)) {\n413\t dropped++;\n414\t }\n415\t score += dropped * 2;\n416\t lockPiece();\n417\t}\n418\t\n419\t// ----- Ghost Piece Position -----\n420\t\n421\tfunction getGhostY(): number {\n422\t if (!currentPiece) return 0;\n423\t let ghostY = currentPiece.pos.y;\n424\t const testPos = { x: currentPiece.pos.x, y: ghostY };\n425\t while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n426\t testPos.y++;\n427\t }\n428\t return testPos.y;\n429\t}\n430\t\n431\t// ----- Lock Piece & Line Clear -----\n432\t\n433\tfunction lockPiece(): void {\n434\t if (!currentPiece) return;\n435\t const { shape, pos, type } = currentPiece;\n436\t\n437\t for (let r = 0; r < shape.length; r++) {\n438\t for (let c = 0; c < shape[r].length; c++) {\n439\t if (!shape[r][c]) continue;\n440\t const by = pos.y + r;\n441\t const bx = pos.x + c;\n442\t if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n443\t board[by][bx] = type;\n444\t }\n445\t }\n446\t }\n447\t\n448\t currentPiece = null;\n449\t\n450\t // Check for completed lines\n451\t const clearedRows: number[] = [];\n452\t for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n453\t if (board[r].every((cell) => cell !== null)) {\n454\t clearedRows.push(r);\n455\t }\n456\t }\n457\t\n458\t if (clearedRows.length > 0) {\n459\t combo++;\n460\t clearing = true;\n461\t // Animate then remove\n462\t animateClear(clearedRows, () => {\n463\t removeLines(clearedRows);\n464\t const n = clearedRows.length;\n465\t const baseScore = LINE_SCORES[n] || 0;\n466\t score += baseScore * (level + 1);\n467\t // Combo bonus\n468\t if (combo > 0) {\n469\t score += 50 * combo * (level + 1);\n470\t }\n471\t linesCleared += n;\n472\t level = Math.floor(linesCleared / 10);\n473\t updateStats();\n474\t clearing = false;\n475\t spawnPiece();\n476\t });\n477\t } else {\n478\t combo = -1;\n479\t updateStats();\n480\t spawnPiece();\n481\t }\n482\t}\n483\t\n484\tfunction animateClear(rows: number[], callback: () => void): void {\n485\t // Mark cells as clearing\n486\t for (const r of rows) {\n487\t for (let c = 0; c < COLS; c++) {\n488\t const boardRow = r - HIDDEN_ROWS;\n489\t if (boardRow >= 0 && boardRow < ROWS) {\n490\t const idx = boardRow * COLS + c;\n491\t cells[idx].classList.add(\"clearing\");\n492\t }\n493\t }\n494\t }\n495\t setTimeout(callback, 400);\n496\t}\n497\t\n498\tfunction removeLines(rows: number[]): void {\n499\t // Sort descending so we remove from bottom up\n500\t rows.sort((a, b) => b - a);\n501\t for (const r of rows) {\n502\t board.splice(r, 1);\n503\t board.unshift(new Array<PieceType | null>(COLS).fill(null));\n504\t }\n505\t}\n506\t\n507\t// ----- Hold -----\n508\t\n509\tfunction holdPiece(): void {\n510\t if (!currentPiece || !canHold) return;\n511\t canHold = false;\n512\t const type = currentPiece.type;\n513\t if (holdType !== null) {\n514\t const prevHold = holdType;\n515\t holdType = type;\n516\t // Spawn the held piece\n517\t currentPiece = null;\n518\t const shape = SHAPES[prevHold][0];\n519\t const x = Math.floor((COLS - shape[0].length) / 2);\n520\t const y = HIDDEN_ROWS - shape.length;\n521\t currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n522\t } else {\n523\t holdType = type;\n524\t currentPiece = null;\n525\t spawnPiece();\n526\t }\n527\t renderHoldPreview();\n528\t}\n529\t\n530\t// ----- Rendering -----\n531\t\n532\tfunction render(): void {\n533\t // Clear all cells\n534\t for (let r = 0; r < ROWS; r++) {\n535\t for (let c = 0; c < COLS; c++) {\n536\t const idx = r * COLS + c;\n537\t const cell = cells[idx];\n538\t const boardR = r + HIDDEN_ROWS;\n539\t // Remove dynamic classes\n540\t cell.className = \"cell\";\n541\t const value = board[boardR][c];\n542\t if (value) {\n543\t cell.classList.add(\"filled\", value);\n544\t }\n545\t }\n546\t }\n547\t\n548\t // Render ghost piece\n549\t if (currentPiece) {\n550\t const ghostY = getGhostY();\n551\t if (ghostY !== currentPiece.pos.y) {\n552\t renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n553\t }\n554\t // Render current piece\n555\t renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n556\t }\n557\t}\n558\t\n559\tfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n560\t for (let r = 0; r < shape.length; r++) {\n561\t for (let c = 0; c < shape[r].length; c++) {\n562\t if (!shape[r][c]) continue;\n563\t const by = pos.y + r;\n564\t const bx = pos.x + c;\n565\t const boardRow = by - HIDDEN_ROWS;\n566\t if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n567\t const idx = boardRow * COLS + bx;\n568\t cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n569\t if (cls === \"ghost\") {\n570\t cells[idx].className = \"cell ghost\";\n571\t }\n572\t }\n573\t }\n574\t }\n575\t}\n576\t\n577\tfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n578\t const shape = SHAPES[type][0];\n579\t // Center the shape in the 4×4 preview\n580\t const offR = Math.floor((4 - shape.length) / 2);\n581\t const offC = Math.floor((4 - shape[0].length) / 2);\n582\t for (let r = 0; r < 4; r++) {\n583\t for (let c = 0; c < 4; c++) {\n584\t const idx = r * 4 + c;\n585\t previewCells[idx].className = \"preview-cell\";\n586\t const sr = r - offR;\n587\t const sc = c - offC;\n588\t if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n589\t previewCells[idx].classList.add(type);\n590\t }\n591\t }\n592\t }\n593\t}\n594\t\n595\tfunction renderHoldPreview(): void {\n596\t if (holdType) {\n597\t renderPreview(holdPreviewCells, holdType);\n598\t } else {\n599\t for (const cell of holdPreviewCells) {\n600\t cell.className = \"preview-cell\";\n601\t }\n602\t }\n603\t}\n604\t\n605\tfunction updateStats(): void {\n606\t document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n607\t document.getElementById(\"level-value\")!.textContent = String(level + 1);\n608\t document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n609\t}\n610\t\n611\t// ----- Drop Speed -----\n612\t\n613\tfunction getDropInterval(): number {\n614\t // Speed curve (milliseconds per drop), gets faster each level\n615\t // Based roughly on NES Tetris speed curve\n616\t const speeds = [\n617\t 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n618\t 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n619\t ];\n620\t if (level < speeds.length) return speeds[level];\n621\t return 5;\n622\t}\n623\t\n624\t// ----- Game Loop -----\n625\t\n626\tfunction gameLoop(timestamp: number): void {\n627\t if (gameOver || paused || !started) return;\n628\t\n629\t if (clearing) {\n630\t animFrameId = requestAnimationFrame(gameLoop);\n631\t return;\n632\t }\n633\t\n634\t if (lastTime === 0) lastTime = timestamp;\n635\t const delta = timestamp - lastTime;\n636\t lastTime = timestamp;\n637\t\n638\t updateDAS(delta);\n639\t\n640\t dropTimer += delta;\n641\t const interval = getDropInterval();\n642\t\n643\t if (dropTimer >= interval) {\n644\t dropTimer -= interval;\n645\t moveDown();\n646\t }\n647\t\n648\t render();\n649\t animFrameId = requestAnimationFrame(gameLoop);\n650\t}\n651\t\n652\t// ----- Input Handling -----\n653\t\n654\t// DAS (Delayed Auto Shift) state\n655\tlet dasKey: string | null = null;\n656\tlet dasTimer: number = 0;\n657\tlet dasActive: boolean = false;\n658\tconst DAS_DELAY = 170; // ms before auto-repeat starts\n659\tconst DAS_RATE = 50; // ms between auto-repeats\n660\t\n661\tfunction handleKeyDown(e: KeyboardEvent): void {\n662\t if (!started || gameOver) {\n663\t if (e.key === \"Enter\" || e.key === \" \") {\n664\t e.preventDefault();\n665\t startGame();\n666\t }\n667\t return;\n668\t }\n669\t\n670\t if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n671\t togglePause();\n672\t return;\n673\t }\n674\t\n675\t if (paused) return;\n676\t\n677\t switch (e.key) {\n678\t case \"ArrowLeft\":\n679\t e.preventDefault();\n680\t if (!dasActive) movePiece(-1, 0);\n681\t dasKey = \"left\";\n682\t dasTimer = 0;\n683\t dasActive = false;\n684\t break;\n685\t case \"ArrowRight\":\n686\t e.preventDefault();\n687\t if (!dasActive) movePiece(1, 0);\n688\t dasKey = \"right\";\n689\t dasTimer = 0;\n690\t dasActive = false;\n691\t break;\n692\t case \"ArrowDown\":\n693\t e.preventDefault();\n694\t if (moveDown()) {\n695\t score += 1;\n696\t updateStats();\n697\t }\n698\t break;\n699\t case \"ArrowUp\":\n700\t case \"x\":\n701\t case \"X\":\n702\t e.preventDefault();\n703\t rotatePiece(1);\n704\t break;\n705\t case \"z\":\n706\t case \"Z\":\n707\t e.preventDefault();\n708\t rotatePiece(-1);\n709\t break;\n710\t case \" \":\n711\t e.preventDefault();\n712\t hardDrop();\n713\t break;\n714\t case \"c\":\n715\t case \"C\":\n716\t case \"Shift\":\n717\t e.preventDefault();\n718\t holdPiece();\n719\t break;\n720\t }\n721\t\n722\t render();\n723\t}\n724\t\n725\tfunction handleKeyUp(e: KeyboardEvent): void {\n726\t if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n727\t if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n728\t}\n729\t\n730\tfunction updateDAS(delta: number): void {\n731\t if (dasKey === null) return;\n732\t dasTimer += delta;\n733\t if (!dasActive && dasTimer >= DAS_DELAY) {\n734\t dasActive = true;\n735\t dasTimer = 0;\n736\t }\n737\t if (dasActive && dasTimer >= DAS_RATE) {\n738\t dasTimer -= DAS_RATE;\n739\t if (dasKey === \"left\") movePiece(-1, 0);\n740\t else if (dasKey === \"right\") movePiece(1, 0);\n741\t render();\n742\t }\n743\t}\n744\t\n745\t// ----- Pause -----\n746\t\n747\tfunction togglePause(): void {\n748\t paused = !paused;\n749\t const overlay = document.getElementById(\"pause-overlay\")!;\n750\t if (paused) {\n751\t overlay.classList.remove(\"hidden\");\n752\t } else {\n753\t overlay.classList.add(\"hidden\");\n754\t lastTime = 0;\n755\t animFrameId = requestAnimationFrame(gameLoop);\n756\t }\n757\t}\n758\t\n759\t// ----- Overlay Management -----\n760\t\n761\tfunction showOverlay(which: \"start\" | \"gameover\"): void {\n762\t const startOverlay = document.getElementById(\"start-overlay\")!;\n763\t const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n764\t\n765\t startOverlay.classList.add(\"hidden\");\n766\t gameoverOverlay.classList.add(\"hidden\");\n767\t\n768\t if (which === \"start\") {\n769\t startOverlay.classList.remove(\"hidden\");\n770\t } else {\n771\t document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n772\t gameoverOverlay.classList.remove(\"hidden\");\n773\t }\n774\t}\n775\t\n776\tfunction hideAllOverlays(): void {\n777\t document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n778\t document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n779\t document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n780\t}\n781\t\n782\t// ----- Start / Restart -----\n783\t\n784\tfunction startGame(): void {\n785\t hideAllOverlays();\n786\t initGame();\n787\t started = true;\n788\t lastTime = 0;\n789\t dropTimer = 0;\n790\t updateStats();\n791\t render();\n792\t animFrameId = requestAnimationFrame(gameLoop);\n793\t}\n794\t\n795\t// ----- Boot -----\n796\t\n797\tfunction boot(): void {\n798\t const boardEl = buildBoardDOM();\n799\t cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n800\t nextPreviewCells = Array.from(\n801\t (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n802\t ) as HTMLDivElement[];\n803\t holdPreviewCells = Array.from(\n804\t (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n805\t ) as HTMLDivElement[];\n806\t\n807\t document.addEventListener(\"keydown\", handleKeyDown);\n808\t document.addEventListener(\"keyup\", handleKeyUp);\n809\t\n810\t initBoard();\n811\t showOverlay(\"start\");\n812\t}\n813\t\n814\tdocument.addEventListener(\"DOMContentLoaded\", boot);\n815\t"}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"42a4f022-a401-47d4-863d-05c406aea067","timestamp":"2026-04-07T02:02:25.147Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","content":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\nlet clearing: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n clearing = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n clearing = true;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n clearing = false;\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (clearing) {\n animFrameId = requestAnimationFrame(gameLoop);\n return;\n }\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","numLines":815,"startLine":1,"totalLines":815}}} +{"type":"assistant","message":{"id":"msg_20260407100226c690728e27ef4a15","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The code looks good. Let me do one final compile and verify everything.\n\nActually, I just realized there's a potential issue with the `buildPreviewDOM` function - it's defined but never called during `boot()`. The preview grids need to be populated with cells. Let me check...\n\nLooking at `boot()`:\n1. `buildBoardDOM()` - builds board cells ✓\n2. Gets `next-preview` cells via querySelectorAll - but those cells don't exist yet!\n3. Gets `hold-preview` cells via querySelectorAll - but those cells don't exist yet!\n\nThe `buildPreviewDOM` function exists but is never called. I need to call it for both previews.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"7c350ae5-ac94-4077-bd88-59537f9673a3"} +{"type":"assistant","message":{"id":"msg_20260407100226c690728e27ef4a15","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"I notice that `buildPreviewDOM` is defined but never called during boot — the preview grids won't have cells. Let me fix that:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"91e41c8d-238b-4b0b-8946-d2071ea32eef"} +{"type":"assistant","message":{"id":"msg_20260407100226c690728e27ef4a15","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_8178435e48b449f78041d052","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","old_string":"function boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];","new_string":"function boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n buildPreviewDOM(\"next-preview\");\n buildPreviewDOM(\"hold-preview\");\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"c5f6ee3d-eacc-42a6-b48d-13840f0d4442"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_8178435e48b449f78041d052","type":"tool_result","content":"The file /tmp/loop-bench-4v9zg5jy/src/tetris.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"daffc7d0-b9c1-4ac4-ab01-812de0f93442","timestamp":"2026-04-07T02:02:44.858Z","tool_use_result":{"filePath":"/tmp/loop-bench-4v9zg5jy/src/tetris.ts","oldString":"function boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];","newString":"function boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n buildPreviewDOM(\"next-preview\");\n buildPreviewDOM(\"hold-preview\");\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];","originalFile":"// ============================================================\n// Tetris – DOM-rendered, TypeScript\n// ============================================================\n\n// ----- Types & Constants -----\n\ntype PieceType = \"I\" | \"O\" | \"T\" | \"S\" | \"Z\" | \"J\" | \"L\";\n\ninterface Position {\n x: number;\n y: number;\n}\n\ninterface ActivePiece {\n type: PieceType;\n shape: number[][];\n pos: Position;\n rotation: number;\n}\n\nconst COLS = 10;\nconst ROWS = 20;\nconst HIDDEN_ROWS = 2; // buffer rows above visible area\n\n// Shapes stored as 4×4 (or smaller) matrices per rotation state\n// Using the SRS-like representations\nconst SHAPES: Record<PieceType, number[][][]> = {\n I: [\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n ],\n [\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n [0, 0, 1, 0],\n ],\n [\n [0, 0, 0, 0],\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n ],\n [\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n [0, 1, 0, 0],\n ],\n ],\n O: [\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n [\n [1, 1],\n [1, 1],\n ],\n ],\n T: [\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n S: [\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 0, 0],\n [0, 1, 1],\n [1, 1, 0],\n ],\n [\n [1, 0, 0],\n [1, 1, 0],\n [0, 1, 0],\n ],\n ],\n Z: [\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 0, 1],\n [0, 1, 1],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 1, 0],\n [1, 1, 0],\n [1, 0, 0],\n ],\n ],\n J: [\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 1],\n [0, 1, 0],\n [0, 1, 0],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [0, 0, 1],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [1, 1, 0],\n ],\n ],\n L: [\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0],\n ],\n [\n [0, 1, 0],\n [0, 1, 0],\n [0, 1, 1],\n ],\n [\n [0, 0, 0],\n [1, 1, 1],\n [1, 0, 0],\n ],\n [\n [1, 1, 0],\n [0, 1, 0],\n [0, 1, 0],\n ],\n ],\n};\n\n// Wall kick data (SRS standard)\n// For J, L, S, T, Z pieces:\nconst WALL_KICKS_JLSTZ: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"1>0\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"1>2\": [[0, 0], [1, 0], [1, -1], [0, 2], [1, 2]],\n \"2>1\": [[0, 0], [-1, 0], [-1, 1], [0, -2], [-1, -2]],\n \"2>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n \"3>2\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"3>0\": [[0, 0], [-1, 0], [-1, -1], [0, 2], [-1, 2]],\n \"0>3\": [[0, 0], [1, 0], [1, 1], [0, -2], [1, -2]],\n};\n\n// For I piece:\nconst WALL_KICKS_I: Record<string, [number, number][]> = {\n \"0>1\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"1>0\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"1>2\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n \"2>1\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"2>3\": [[0, 0], [2, 0], [-1, 0], [2, 1], [-1, -2]],\n \"3>2\": [[0, 0], [-2, 0], [1, 0], [-2, -1], [1, 2]],\n \"3>0\": [[0, 0], [1, 0], [-2, 0], [1, -2], [-2, 1]],\n \"0>3\": [[0, 0], [-1, 0], [2, 0], [-1, 2], [2, -1]],\n};\n\n// Scoring table (NES-style)\nconst LINE_SCORES: Record<number, number> = {\n 1: 100,\n 2: 300,\n 3: 500,\n 4: 800,\n};\n\nconst PIECE_TYPES: PieceType[] = [\"I\", \"O\", \"T\", \"S\", \"Z\", \"J\", \"L\"];\n\n// ----- DOM Setup -----\n\nfunction createCell(className: string): HTMLDivElement {\n const div = document.createElement(\"div\");\n div.className = className;\n return div;\n}\n\nfunction buildBoardDOM(): HTMLDivElement {\n const boardEl = document.getElementById(\"board\") as HTMLDivElement;\n boardEl.innerHTML = \"\";\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n boardEl.appendChild(createCell(\"cell\"));\n }\n }\n return boardEl;\n}\n\nfunction buildPreviewDOM(id: string): HTMLDivElement {\n const el = document.getElementById(id) as HTMLDivElement;\n el.innerHTML = \"\";\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n el.appendChild(createCell(\"preview-cell\"));\n }\n }\n return el;\n}\n\n// ----- Game State -----\n\nlet board: (PieceType | null)[][];\nlet cells: HTMLDivElement[];\nlet nextPreviewCells: HTMLDivElement[];\nlet holdPreviewCells: HTMLDivElement[];\n\nlet currentPiece: ActivePiece | null;\nlet nextType: PieceType;\nlet holdType: PieceType | null;\nlet canHold: boolean;\n\nlet score: number;\nlet level: number;\nlet linesCleared: number;\nlet combo: number;\n\nlet gameOver: boolean;\nlet paused: boolean;\nlet started: boolean;\nlet clearing: boolean;\n\nlet dropTimer: number;\nlet lastTime: number;\nlet animFrameId: number;\n\n// Bag randomizer for fair piece distribution\nlet bag: PieceType[] = [];\n\n// ----- Initialization -----\n\nfunction initBoard(): void {\n board = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n board.push(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\nfunction initGame(): void {\n initBoard();\n score = 0;\n level = 0;\n linesCleared = 0;\n combo = -1;\n gameOver = false;\n paused = false;\n clearing = false;\n holdType = null;\n canHold = true;\n bag = [];\n nextType = pullFromBag();\n currentPiece = null;\n dropTimer = 0;\n lastTime = 0;\n spawnPiece();\n}\n\n// ----- Bag Randomizer -----\n\nfunction fillBag(): void {\n const b = [...PIECE_TYPES];\n // Fisher-Yates shuffle\n for (let i = b.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [b[i], b[j]] = [b[j], b[i]];\n }\n bag = b;\n}\n\nfunction pullFromBag(): PieceType {\n if (bag.length === 0) fillBag();\n return bag.pop()!;\n}\n\n// ----- Piece Spawning -----\n\nfunction spawnPiece(): void {\n const type = nextType;\n nextType = pullFromBag();\n canHold = true;\n\n const shape = SHAPES[type][0];\n // Spawn centered, in the hidden rows area\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length; // try to fit in hidden area\n\n currentPiece = { type, shape, pos: { x, y }, rotation: 0 };\n\n if (!isValid(shape, currentPiece.pos)) {\n // Game over – can't spawn\n gameOver = true;\n currentPiece = null;\n showOverlay(\"gameover\");\n }\n\n renderPreview(nextPreviewCells, nextType);\n renderHoldPreview();\n}\n\n// ----- Collision Detection -----\n\nfunction isValid(shape: number[][], pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const bx = pos.x + c;\n const by = pos.y + r;\n if (bx < 0 || bx >= COLS || by >= ROWS + HIDDEN_ROWS) return false;\n if (by >= 0 && board[by][bx] !== null) return false;\n }\n }\n return true;\n}\n\n// ----- Movement -----\n\nfunction movePiece(dx: number, dy: number): boolean {\n if (!currentPiece) return false;\n const newPos = { x: currentPiece.pos.x + dx, y: currentPiece.pos.y + dy };\n if (isValid(currentPiece.shape, newPos)) {\n currentPiece.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction moveDown(): boolean {\n if (!currentPiece) return false;\n if (movePiece(0, 1)) return true;\n lockPiece();\n return false;\n}\n\n// ----- Rotation with Wall Kicks (SRS) -----\n\nfunction rotatePiece(dir: 1 | -1): void {\n if (!currentPiece) return;\n const oldRot = currentPiece.rotation;\n const newRot = (oldRot + dir + 4) % 4;\n const newShape = SHAPES[currentPiece.type][newRot];\n\n const kickKey = `${oldRot}>${newRot}`;\n const kicks = currentPiece.type === \"I\"\n ? (WALL_KICKS_I[kickKey] || [[0, 0]])\n : (WALL_KICKS_JLSTZ[kickKey] || [[0, 0]]);\n\n for (const [kx, ky] of kicks) {\n const testPos = { x: currentPiece.pos.x + kx, y: currentPiece.pos.y - ky };\n if (isValid(newShape, testPos)) {\n currentPiece.shape = newShape;\n currentPiece.pos = testPos;\n currentPiece.rotation = newRot;\n return;\n }\n }\n // Rotation failed – no valid kick\n}\n\n// ----- Hard Drop -----\n\nfunction hardDrop(): void {\n if (!currentPiece) return;\n let dropped = 0;\n while (movePiece(0, 1)) {\n dropped++;\n }\n score += dropped * 2;\n lockPiece();\n}\n\n// ----- Ghost Piece Position -----\n\nfunction getGhostY(): number {\n if (!currentPiece) return 0;\n let ghostY = currentPiece.pos.y;\n const testPos = { x: currentPiece.pos.x, y: ghostY };\n while (isValid(currentPiece.shape, { x: testPos.x, y: testPos.y + 1 })) {\n testPos.y++;\n }\n return testPos.y;\n}\n\n// ----- Lock Piece & Line Clear -----\n\nfunction lockPiece(): void {\n if (!currentPiece) return;\n const { shape, pos, type } = currentPiece;\n\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n if (by >= 0 && by < ROWS + HIDDEN_ROWS && bx >= 0 && bx < COLS) {\n board[by][bx] = type;\n }\n }\n }\n\n currentPiece = null;\n\n // Check for completed lines\n const clearedRows: number[] = [];\n for (let r = 0; r < ROWS + HIDDEN_ROWS; r++) {\n if (board[r].every((cell) => cell !== null)) {\n clearedRows.push(r);\n }\n }\n\n if (clearedRows.length > 0) {\n combo++;\n clearing = true;\n // Animate then remove\n animateClear(clearedRows, () => {\n removeLines(clearedRows);\n const n = clearedRows.length;\n const baseScore = LINE_SCORES[n] || 0;\n score += baseScore * (level + 1);\n // Combo bonus\n if (combo > 0) {\n score += 50 * combo * (level + 1);\n }\n linesCleared += n;\n level = Math.floor(linesCleared / 10);\n updateStats();\n clearing = false;\n spawnPiece();\n });\n } else {\n combo = -1;\n updateStats();\n spawnPiece();\n }\n}\n\nfunction animateClear(rows: number[], callback: () => void): void {\n // Mark cells as clearing\n for (const r of rows) {\n for (let c = 0; c < COLS; c++) {\n const boardRow = r - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS) {\n const idx = boardRow * COLS + c;\n cells[idx].classList.add(\"clearing\");\n }\n }\n }\n setTimeout(callback, 400);\n}\n\nfunction removeLines(rows: number[]): void {\n // Sort descending so we remove from bottom up\n rows.sort((a, b) => b - a);\n for (const r of rows) {\n board.splice(r, 1);\n board.unshift(new Array<PieceType | null>(COLS).fill(null));\n }\n}\n\n// ----- Hold -----\n\nfunction holdPiece(): void {\n if (!currentPiece || !canHold) return;\n canHold = false;\n const type = currentPiece.type;\n if (holdType !== null) {\n const prevHold = holdType;\n holdType = type;\n // Spawn the held piece\n currentPiece = null;\n const shape = SHAPES[prevHold][0];\n const x = Math.floor((COLS - shape[0].length) / 2);\n const y = HIDDEN_ROWS - shape.length;\n currentPiece = { type: prevHold, shape, pos: { x, y }, rotation: 0 };\n } else {\n holdType = type;\n currentPiece = null;\n spawnPiece();\n }\n renderHoldPreview();\n}\n\n// ----- Rendering -----\n\nfunction render(): void {\n // Clear all cells\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n const idx = r * COLS + c;\n const cell = cells[idx];\n const boardR = r + HIDDEN_ROWS;\n // Remove dynamic classes\n cell.className = \"cell\";\n const value = board[boardR][c];\n if (value) {\n cell.classList.add(\"filled\", value);\n }\n }\n }\n\n // Render ghost piece\n if (currentPiece) {\n const ghostY = getGhostY();\n if (ghostY !== currentPiece.pos.y) {\n renderShape(currentPiece.shape, { x: currentPiece.pos.x, y: ghostY }, \"ghost\", currentPiece.type);\n }\n // Render current piece\n renderShape(currentPiece.shape, currentPiece.pos, \"filled\", currentPiece.type);\n }\n}\n\nfunction renderShape(shape: number[][], pos: Position, cls: string, type: PieceType): void {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const by = pos.y + r;\n const bx = pos.x + c;\n const boardRow = by - HIDDEN_ROWS;\n if (boardRow >= 0 && boardRow < ROWS && bx >= 0 && bx < COLS) {\n const idx = boardRow * COLS + bx;\n cells[idx].className = `cell ${cls} ${cls === \"ghost\" ? \"\" : type}`.trim();\n if (cls === \"ghost\") {\n cells[idx].className = \"cell ghost\";\n }\n }\n }\n }\n}\n\nfunction renderPreview(previewCells: HTMLDivElement[], type: PieceType): void {\n const shape = SHAPES[type][0];\n // Center the shape in the 4×4 preview\n const offR = Math.floor((4 - shape.length) / 2);\n const offC = Math.floor((4 - shape[0].length) / 2);\n for (let r = 0; r < 4; r++) {\n for (let c = 0; c < 4; c++) {\n const idx = r * 4 + c;\n previewCells[idx].className = \"preview-cell\";\n const sr = r - offR;\n const sc = c - offC;\n if (sr >= 0 && sr < shape.length && sc >= 0 && sc < shape[sr].length && shape[sr][sc]) {\n previewCells[idx].classList.add(type);\n }\n }\n }\n}\n\nfunction renderHoldPreview(): void {\n if (holdType) {\n renderPreview(holdPreviewCells, holdType);\n } else {\n for (const cell of holdPreviewCells) {\n cell.className = \"preview-cell\";\n }\n }\n}\n\nfunction updateStats(): void {\n document.getElementById(\"score-value\")!.textContent = score.toLocaleString();\n document.getElementById(\"level-value\")!.textContent = String(level + 1);\n document.getElementById(\"lines-value\")!.textContent = String(linesCleared);\n}\n\n// ----- Drop Speed -----\n\nfunction getDropInterval(): number {\n // Speed curve (milliseconds per drop), gets faster each level\n // Based roughly on NES Tetris speed curve\n const speeds = [\n 800, 720, 630, 550, 470, 380, 300, 220, 150, 100,\n 80, 70, 60, 50, 40, 30, 20, 15, 10, 8,\n ];\n if (level < speeds.length) return speeds[level];\n return 5;\n}\n\n// ----- Game Loop -----\n\nfunction gameLoop(timestamp: number): void {\n if (gameOver || paused || !started) return;\n\n if (clearing) {\n animFrameId = requestAnimationFrame(gameLoop);\n return;\n }\n\n if (lastTime === 0) lastTime = timestamp;\n const delta = timestamp - lastTime;\n lastTime = timestamp;\n\n updateDAS(delta);\n\n dropTimer += delta;\n const interval = getDropInterval();\n\n if (dropTimer >= interval) {\n dropTimer -= interval;\n moveDown();\n }\n\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Input Handling -----\n\n// DAS (Delayed Auto Shift) state\nlet dasKey: string | null = null;\nlet dasTimer: number = 0;\nlet dasActive: boolean = false;\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\n\nfunction handleKeyDown(e: KeyboardEvent): void {\n if (!started || gameOver) {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n startGame();\n }\n return;\n }\n\n if (e.key === \"p\" || e.key === \"P\" || e.key === \"Escape\") {\n togglePause();\n return;\n }\n\n if (paused) return;\n\n switch (e.key) {\n case \"ArrowLeft\":\n e.preventDefault();\n if (!dasActive) movePiece(-1, 0);\n dasKey = \"left\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowRight\":\n e.preventDefault();\n if (!dasActive) movePiece(1, 0);\n dasKey = \"right\";\n dasTimer = 0;\n dasActive = false;\n break;\n case \"ArrowDown\":\n e.preventDefault();\n if (moveDown()) {\n score += 1;\n updateStats();\n }\n break;\n case \"ArrowUp\":\n case \"x\":\n case \"X\":\n e.preventDefault();\n rotatePiece(1);\n break;\n case \"z\":\n case \"Z\":\n e.preventDefault();\n rotatePiece(-1);\n break;\n case \" \":\n e.preventDefault();\n hardDrop();\n break;\n case \"c\":\n case \"C\":\n case \"Shift\":\n e.preventDefault();\n holdPiece();\n break;\n }\n\n render();\n}\n\nfunction handleKeyUp(e: KeyboardEvent): void {\n if (e.key === \"ArrowLeft\" && dasKey === \"left\") dasKey = null;\n if (e.key === \"ArrowRight\" && dasKey === \"right\") dasKey = null;\n}\n\nfunction updateDAS(delta: number): void {\n if (dasKey === null) return;\n dasTimer += delta;\n if (!dasActive && dasTimer >= DAS_DELAY) {\n dasActive = true;\n dasTimer = 0;\n }\n if (dasActive && dasTimer >= DAS_RATE) {\n dasTimer -= DAS_RATE;\n if (dasKey === \"left\") movePiece(-1, 0);\n else if (dasKey === \"right\") movePiece(1, 0);\n render();\n }\n}\n\n// ----- Pause -----\n\nfunction togglePause(): void {\n paused = !paused;\n const overlay = document.getElementById(\"pause-overlay\")!;\n if (paused) {\n overlay.classList.remove(\"hidden\");\n } else {\n overlay.classList.add(\"hidden\");\n lastTime = 0;\n animFrameId = requestAnimationFrame(gameLoop);\n }\n}\n\n// ----- Overlay Management -----\n\nfunction showOverlay(which: \"start\" | \"gameover\"): void {\n const startOverlay = document.getElementById(\"start-overlay\")!;\n const gameoverOverlay = document.getElementById(\"gameover-overlay\")!;\n\n startOverlay.classList.add(\"hidden\");\n gameoverOverlay.classList.add(\"hidden\");\n\n if (which === \"start\") {\n startOverlay.classList.remove(\"hidden\");\n } else {\n document.getElementById(\"final-score\")!.textContent = score.toLocaleString();\n gameoverOverlay.classList.remove(\"hidden\");\n }\n}\n\nfunction hideAllOverlays(): void {\n document.getElementById(\"start-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"gameover-overlay\")!.classList.add(\"hidden\");\n document.getElementById(\"pause-overlay\")!.classList.add(\"hidden\");\n}\n\n// ----- Start / Restart -----\n\nfunction startGame(): void {\n hideAllOverlays();\n initGame();\n started = true;\n lastTime = 0;\n dropTimer = 0;\n updateStats();\n render();\n animFrameId = requestAnimationFrame(gameLoop);\n}\n\n// ----- Boot -----\n\nfunction boot(): void {\n const boardEl = buildBoardDOM();\n cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];\n nextPreviewCells = Array.from(\n (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n holdPreviewCells = Array.from(\n (document.getElementById(\"hold-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")\n ) as HTMLDivElement[];\n\n document.addEventListener(\"keydown\", handleKeyDown);\n document.addEventListener(\"keyup\", handleKeyUp);\n\n initBoard();\n showOverlay(\"start\");\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", boot);\n","structuredPatch":[{"oldStart":797,"oldLines":6,"newStart":797,"newLines":8,"lines":[" function boot(): void {"," const boardEl = buildBoardDOM();"," cells = Array.from(boardEl.querySelectorAll(\".cell\")) as HTMLDivElement[];","+ buildPreviewDOM(\"next-preview\");","+ buildPreviewDOM(\"hold-preview\");"," nextPreviewCells = Array.from("," (document.getElementById(\"next-preview\") as HTMLDivElement).querySelectorAll(\".preview-cell\")"," ) as HTMLDivElement[];"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_2026040710024638f0af6f0c4b4e9a","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_18f909cb87d842ab967f23ce","name":"Bash","input":{"command":"tsc --project tsconfig.json 2>&1","description":"Final TypeScript compilation"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"3cb677ee-af9a-4486-8c72-f795be2f320e"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_18f909cb87d842ab967f23ce","type":"tool_result","content":"[rerun: b10]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"b1c49eab-0b00-469f-b007-e699e6b685ee","timestamp":"2026-04-07T02:02:51.542Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071002533d31149c0f0445c9","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Clean compile. Let me verify the final file structure:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"adb150bb-4509-45b3-ad8d-34e81067511b"} +{"type":"assistant","message":{"id":"msg_202604071002533d31149c0f0445c9","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_e2f5bf4724534a41a6277a8f","name":"Bash","input":{"command":"ls -la /tmp/loop-bench-4v9zg5jy/src/ && echo \"---\" && wc -l /tmp/loop-bench-4v9zg5jy/src/tetris.ts /tmp/loop-bench-4v9zg5jy/src/tetris.js","description":"Verify final file structure and sizes"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"bbf6c0b4-07d7-4c85-ae7a-3241bcc59fcf"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_e2f5bf4724534a41a6277a8f","type":"tool_result","content":"total 60\ndrwxr-xr-x 2 root root 4096 Apr 7 04:02 .\ndrwx------ 6 root root 4096 Apr 7 03:59 ..\n-rw-r--r-- 1 root root 2331 Apr 7 03:59 index.html\n-rw-r--r-- 1 root root 4998 Apr 7 03:56 styles.css\n-rw-r--r-- 1 root root 18028 Apr 7 04:02 tetris.js\n-rw-r--r-- 1 root root 18765 Apr 7 04:02 tetris.ts\n---\n 816 /tmp/loop-bench-4v9zg5jy/src/tetris.ts\n 687 /tmp/loop-bench-4v9zg5jy/src/tetris.js\n 1503 total\n[rerun: b11]","is_error":false}]},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"ccda954a-0a73-4655-8807-a027a43b13e3","timestamp":"2026-04-07T02:03:03.094Z","tool_use_result":{"stdout":"total 60\ndrwxr-xr-x 2 root root 4096 Apr 7 04:02 .\ndrwx------ 6 root root 4096 Apr 7 03:59 ..\n-rw-r--r-- 1 root root 2331 Apr 7 03:59 index.html\n-rw-r--r-- 1 root root 4998 Apr 7 03:56 styles.css\n-rw-r--r-- 1 root root 18028 Apr 7 04:02 tetris.js\n-rw-r--r-- 1 root root 18765 Apr 7 04:02 tetris.ts\n---\n 816 /tmp/loop-bench-4v9zg5jy/src/tetris.ts\n 687 /tmp/loop-bench-4v9zg5jy/src/tetris.js\n 1503 total","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407100304c2fed9f593d34882","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Everything is built. Here's a summary of what was created:\n\n## Files\n\n| File | Purpose |\n|---|---|\n| `src/index.html` | Game page with board, side panels, overlays |\n| `src/styles.css` | Dark theme styling, piece colors, animations |\n| `src/tetris.ts` | Complete game engine in TypeScript (816 lines) |\n| `src/tetris.js` | Compiled JavaScript output |\n| `tsconfig.json` | TypeScript configuration |\n\n## Game Features\n\n**Standard Tetris Mechanics:**\n- **7-bag randomizer** — fair piece distribution using Fisher-Yates shuffle\n- **SRS rotation system** — full Super Rotation System with wall kick tables for all 7 pieces (I, O, T, S, Z, J, L)\n- **Ghost piece** — translucent preview showing where the piece will land\n- **Hold piece** — stash a piece for later (once per drop)\n- **Next piece preview** — shows the upcoming piece\n- **Line clearing** with flash animation\n- **Combo scoring** — bonus points for consecutive clears\n- **NES-style scoring** — 100/300/500/800 × (level+1) for 1/2/3/4 lines, plus soft/hard drop bonuses\n- **20 speed levels** — drop interval decreases from 800ms down to 5ms\n\n**Controls:**\n| Key | Action |\n|---|---|\n| ← → | Move left/right |\n| ↑ / X | Rotate clockwise |\n| Z | Rotate counter-clockwise |\n| ↓ | Soft drop (+1 point per row) |\n| Space | Hard drop (+2 points per row) |\n| C / Shift | Hold piece |\n| P / Esc | Pause |\n\n**Rendering:** Entirely DOM-based — the 10×20 grid is CSS Grid with styled `<div>` cells, and piece colors are applied via CSS classes with inset box-shadow for a 3D beveled look.\n\nOpen `src/index.html` in a browser to play."}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","uuid":"b16c0509-89b8-4b3d-b049-7254fcb391d2"} +{"type":"result","subtype":"success","is_error":false,"duration_ms":775970,"duration_api_ms":770494,"num_turns":34,"result":"Everything is built. Here's a summary of what was created:\n\n## Files\n\n| File | Purpose |\n|---|---|\n| `src/index.html` | Game page with board, side panels, overlays |\n| `src/styles.css` | Dark theme styling, piece colors, animations |\n| `src/tetris.ts` | Complete game engine in TypeScript (816 lines) |\n| `src/tetris.js` | Compiled JavaScript output |\n| `tsconfig.json` | TypeScript configuration |\n\n## Game Features\n\n**Standard Tetris Mechanics:**\n- **7-bag randomizer** — fair piece distribution using Fisher-Yates shuffle\n- **SRS rotation system** — full Super Rotation System with wall kick tables for all 7 pieces (I, O, T, S, Z, J, L)\n- **Ghost piece** — translucent preview showing where the piece will land\n- **Hold piece** — stash a piece for later (once per drop)\n- **Next piece preview** — shows the upcoming piece\n- **Line clearing** with flash animation\n- **Combo scoring** — bonus points for consecutive clears\n- **NES-style scoring** — 100/300/500/800 × (level+1) for 1/2/3/4 lines, plus soft/hard drop bonuses\n- **20 speed levels** — drop interval decreases from 800ms down to 5ms\n\n**Controls:**\n| Key | Action |\n|---|---|\n| ← → | Move left/right |\n| ↑ / X | Rotate clockwise |\n| Z | Rotate counter-clockwise |\n| ↓ | Soft drop (+1 point per row) |\n| Space | Hard drop (+2 points per row) |\n| C / Shift | Hold piece |\n| P / Esc | Pause |\n\n**Rendering:** Entirely DOM-based — the 10×20 grid is CSS Grid with styled `<div>` cells, and piece colors are applied via CSS classes with inset box-shadow for a 3D beveled look.\n\nOpen `src/index.html` in a browser to play.","stop_reason":"end_turn","session_id":"35cf97aa-a5f1-432a-acb7-3e3f74501418","total_cost_usd":0.5418750000000001,"usage":{"input_tokens":27875,"cache_creation_input_tokens":0,"cache_read_input_tokens":500800,"output_tokens":20534,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{"glm-5.1":{"inputTokens":27875,"outputTokens":20534,"cacheReadInputTokens":500800,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.5418750000000001,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"8af8145e-9c05-4f26-b4b6-966853a558cc"} diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/claude_output.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/claude_output.json @@ -0,0 +1 @@ +{"type":"assistant","message":{"id":"msg_20260407095053507b00bf35da40ed","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The user wants me to build a playable Tetris game that runs in a browser with:\n- Standard Tetris mechanics (piece rotation, line clearing, scoring, increasing speed)\n- Keyboard controls\n- TypeScript\n- WebGL rendering\n\nLet me plan this out:\n\n1. I'll create a single HTML file that contains the TypeScript (compiled to JS) or I could set up a proper TypeScript project. Since they want TypeScript, let me create a proper project structure.\n\n2. Actually, for simplicity and to make it runnable directly, I'll create an HTML file and a TypeScript file, then compile the TypeScript. But let me think about the best approach.\n\nLet me create:\n- A project directory with `index.html`\n- A `game.ts` file with all the game logic and WebGL rendering\n- A `tsconfig.json` for TypeScript compilation\n- Use a simple build approach\n\nActually, the simplest approach that still uses TypeScript properly:\n1. Create `index.html` with the canvas and UI\n2. Create `game.ts` with all game logic and WebGL rendering\n3. Compile with `tsc`\n4. Serve or open directly\n\nLet me design the game:\n\n**Tetris pieces (Tetrominoes):**\n- I, O, T, S, Z, J, L\n\n**Game mechanics:**\n- 10 columns × 20 rows grid\n- Pieces fall from top\n- Rotation (SRS - Super Rotation System or simple rotation)\n- Line clearing with scoring\n- Speed increases with level\n- Game over when pieces stack to top\n\n**Scoring:**\n- Single: 100 × level\n- Double: 300 × level\n- Triple: 500 × level\n- Tetris: 800 × level\n\n**WebGL Rendering:**\n- Draw colored quads for each cell\n- Use shaders to render blocks with nice colors and borders\n\n**Controls:**\n- Left/Right arrows: move\n- Up arrow: rotate\n- Down arrow: soft drop\n- Space: hard drop\n\nLet me write this out. I'll create a comprehensive implementation.\n\nFor WebGL, I'll:\n1. Create a simple 2D shader program\n2. Draw each block as a colored rectangle\n3. Add grid lines\n4. Add glow/border effects\n\nLet me think about the WebGL approach more carefully:\n- I'll use a simple vertex/fragment shader pair\n- Each block will be drawn as two triangles (a quad)\n- I'll pass color and position as uniforms or attributes\n- For efficiency, I can batch all quads into a single draw call\n\nLet me structure the TypeScript code:\n\n```typescript\n// Types and constants\n// WebGL setup\n// Game state management\n// Piece definitions and rotation\n// Game loop\n// Input handling\n// Rendering\n// Scoring\n```\n\nLet me write the complete implementation now.\n\nI'll create:\n1. `index.html` - HTML structure with canvas, score display, etc.\n2. `src/game.ts` - Main game code in TypeScript\n3. `tsconfig.json` - TypeScript config\n4. `package.json` - For dependencies\n\nActually, to keep it simple and self-contained, let me create just the HTML and TS files, with the TS compiled to JS that the HTML loads.\n\nLet me think about the WebGL rendering approach more carefully:\n\nI'll use instanced rendering or just batched vertex buffers. For simplicity, I'll build a vertex buffer each frame with all the quads.\n\nEach quad = 2 triangles = 6 vertices. Each vertex has position (x, y) and color (r, g, b).\n\nFor the grid (10×20), plus current piece, plus ghost piece, plus next piece preview, this is very manageable.\n\nLet me write the shaders:\n\nVertex shader:\n```glsl\nattribute vec2 a_position;\nattribute vec4 a_color;\nvarying vec4 v_color;\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_color = a_color;\n}\n```\n\nFragment shader:\n```glsl\nprecision mediump float;\nvarying vec4 v_color;\nvoid main() {\n gl_FragColor = v_color;\n}\n```\n\nFor nice-looking blocks, I can add a slight border/3D effect by drawing each block as a slightly inset colored rect with a darker border.\n\nActually, I can make it look nicer by passing texture coordinates and computing the border in the fragment shader.\n\nLet me revise - I'll pass position, color, and a \"local\" coordinate (0-1 within each block) so I can compute borders and shading in the fragment shader.\n\nEach vertex: x, y, u, v, r, g, b, a\nWhere u,v are 0-1 local coordinates within the block.\n\nFragment shader can then:\n- Darken edges for a 3D effect\n- Add a highlight on top-left\n- Add shadow on bottom-right\n\nThis will make it look much nicer than flat colored blocks.\n\nLet me plan the full implementation:\n\n```\nBoard: 10 wide × 20 tall (+ 2 invisible rows at top for spawning)\nCell size: will be calculated to fit the canvas\n\nPieces:\n I: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]]\n O: [[1,1],[1,1]]\n T: [[0,1,0],[1,1,1],[0,0,0]]\n S: [[0,1,1],[1,1,0],[0,0,0]]\n Z: [[1,1,0],[0,1,1],[0,0,0]]\n J: [[1,0,0],[1,1,1],[0,0,0]]\n L: [[0,0,1],[1,1,1],[0,0,0]]\n\nColors:\n I: cyan\n O: yellow\n T: purple\n S: green\n Z: red\n J: blue\n L: orange\n\nRotation: Simple clockwise/counter-clockwise rotation of the matrix\n\nGame loop:\n- Every tick (speed based on level), move piece down\n- If can't move down, lock piece\n- Check for completed lines\n- Update score\n- Spawn new piece\n- If can't spawn, game over\n\nLevel increases every 10 lines cleared.\nSpeed formula: frames per tick decreasing with level\n```\n\nAlright, let me write the code. I'll create a single TypeScript file with everything.\n\nFor the project setup:\n- No npm dependencies needed (pure TypeScript/JavaScript)\n- Just need TypeScript compiler\n\nLet me create the files:\n\n1. First, let me check what's available in the environment.\n\nActually, let me just start creating the files. I'll make a clean project structure.\n\nLet me think about the canvas sizing:\n- Board: 10 × 20 cells\n- I'll add a sidebar for score, next piece, level\n- Canvas could be something like 400×800 for the board area\n\nActually, let me make the canvas fill the center of the screen and calculate cell sizes dynamically.\n\nLet me write this now. I'll create:\n- `/tmp/loop-bench-l7ah2axi/tetris/index.html`\n- `/tmp/loop-bench-l7ah2axi/tetris/tsconfig.json`\n- `/tmp/loop-bench-l7ah2axi/tetris/src/game.ts`\n\nThen compile and test.\n\nLet me think about the WebGL rendering more carefully. I want nice-looking blocks.\n\nFor each filled cell, I'll draw a quad with:\n- A base color (the piece color)\n- A lighter highlight on the top and left edges (3D effect)\n- A darker shadow on the bottom and right edges\n\nI can achieve this by passing local UV coordinates (0 to 1 within each block) and computing the shading in the fragment shader.\n\nFor the background grid, I'll draw subtle lines.\n\nLet me write the vertex format:\n- position: vec2 (screen coordinates in clip space)\n- localUV: vec2 (0-1 within the block for shading)\n- color: vec4 (base color)\n\nTotal: 8 floats per vertex, 6 vertices per quad = 48 floats per block.\n\nFor a full board (200 cells max) + current piece (4 cells) + ghost (4 cells) + grid lines, this is very small.\n\nLet me code this up properly.\n\nActually, I realize I should think about the coordinate system. In WebGL, clip space goes from -1 to 1. I'll map the game board to clip space.\n\nBoard layout:\n- 10 columns × 20 rows\n- Cell size in clip space: I'll calculate based on canvas aspect ratio\n\nActually, it's easier to work in pixel coordinates and use a projection matrix or just convert to clip space.\n\nLet me use a simpler approach: work in \"game coordinates\" where (0,0) is top-left of the board, and convert to clip space in the vertex shader using uniforms for the board dimensions.\n\nHmm, actually the simplest approach for this game:\n1. Define board dimensions in pixels\n2. Convert pixel positions to clip space: clipX = (pixelX / canvasWidth) * 2 - 1, clipY = 1 - (pixelY / canvasHeight) * 2\n\nOr I can pass a projection uniform to the shader.\n\nLet me keep it simple: I'll calculate clip-space coordinates directly in JavaScript and pass them as vertex attributes.\n\nOK let me just write the code now. I've been planning long enough.\n\nLet me structure the TypeScript code with classes:\n\n```typescript\n// Constants\nconst BOARD_WIDTH = 10;\nconst BOARD_HEIGHT = 20;\nconst HIDDEN_ROWS = 2; // extra rows above visible area for spawning\n\n// Piece types\nenum PieceType { I, O, T, S, Z, J, L }\n\n// Piece definitions (rotation states)\n// Colors\n\nclass Piece {\n type: PieceType;\n x: number;\n y: number;\n rotation: number;\n shape: number[][];\n}\n\nclass Game {\n board: number[][]; // 0 = empty, 1-7 = piece type+1\n currentPiece: Piece | null;\n nextPiece: Piece | null;\n score: number;\n level: number;\n linesCleared: number;\n gameOver: boolean;\n // ...\n}\n\nclass Renderer {\n gl: WebGLRenderingContext;\n program: WebGLProgram;\n // ...\n}\n\n// Input handler\n// Game loop\n```\n\nLet me write the complete code now. I'll aim for clean, well-structured TypeScript with good WebGL rendering.\n\nHere's my plan for the files:\n\n**index.html**: Basic HTML with canvas, score display, controls info\n**src/game.ts**: All game logic and rendering in one file\n\nLet me start coding. I'll write a comprehensive implementation.\n\nFor the piece rotation, I'll use a simple matrix rotation (transpose + reverse for clockwise).\n\nFor wall kicks, I'll implement basic wall kicks (try offsets when rotation would cause collision).\n\nLet me think about the exact pixel layout:\n- Canvas: let's say 480 × 640 pixels\n- Board area: 300 × 600 pixels (30px per cell), positioned on the left\n- Sidebar: remaining width on the right for score, next piece, level\n\nActually, I'll make it responsive. Let me use the full viewport and calculate sizes dynamically.\n\nLet me just write the code now. Here's my full implementation plan:\n\n```\nTetromino shapes as 2D arrays for each rotation state\nOR compute rotation from a single shape definition\n\nI'll define each piece as a 2D boolean/int array and compute rotations.\n\nPiece colors:\nI = cyan (#00FFFF)\nO = yellow (#FFFF00)\nT = purple (#800080) or (#AA00FF)\nS = green (#00FF00)\nZ = red (#FF0000)\nJ = blue (#0000FF)\nL = orange (#FF8000)\n\nBoard representation:\n- 2D array, 0 = empty, 1-7 = filled (piece type + 1)\n\nGhost piece: show where the piece will land (semi-transparent)\n\nNext piece preview: show the next piece in the sidebar\n\nScoring:\n- Soft drop: 1 point per cell\n- Hard drop: 2 points per cell\n- Single line: 100 × level\n- Double: 300 × level\n- Triple: 500 × level\n- Tetris: 800 × level\n\nLevel up every 10 lines.\n\nSpeed:\n- Level 1: 1000ms per tick\n- Level 2: 900ms\n- ...\n- Level 10: 100ms\n- Formula: Math.max(100, 1000 - (level - 1) * 100)\n- Or use the NES formula: frames per row\n\nI'll use: delay = Math.max(50, 1000 * Math.pow(0.85, level - 1))\n\nThis gives:\nLevel 1: 1000ms\nLevel 2: 850ms\nLevel 3: 722ms\nLevel 5: 522ms\nLevel 10: 232ms\nLevel 15: 103ms\nLevel 20: 46ms (capped at 50)\n\nThat feels about right.\n\nControls:\n- Left arrow: move left\n- Right arrow: move right\n- Up arrow or X: rotate clockwise\n- Z: rotate counter-clockwise\n- Down arrow: soft drop\n- Space: hard drop\n- P: pause\n```\n\nOK, now let me actually write the code. I'll create a comprehensive, well-structured game.\n\nFor the WebGL rendering, I want to make it look really nice:\n\n1. Background: dark gray/black with subtle grid lines\n2. Placed blocks: solid colors with 3D beveled edge effect\n3. Current piece: same as placed blocks\n4. Ghost piece: semi-transparent version of current piece color\n5. Next piece preview: in the sidebar\n6. Border around the playing field\n\nFor the 3D beveled effect on blocks, I'll use the local UV coordinates:\n- Top edge: lighter\n- Left edge: lighter\n- Bottom edge: darker\n- Right edge: darker\n- Center: base color\n- Small border around each block\n\nHere's the fragment shader approach:\n\n```glsl\nprecision mediump float;\nvarying vec2 v_localUV;\nvarying vec4 v_color;\nvarying float v_isBlock;\n\nvoid main() {\n if (v_isBlock < 0.5) {\n // Grid line or background\n gl_FragColor = v_color;\n return;\n }\n \n vec2 uv = v_localUV;\n float border = 0.08;\n float highlight = 0.3;\n float shadow = 0.3;\n \n vec3 baseColor = v_color.rgb;\n vec3 color = baseColor;\n \n // Inner area\n if (uv.x > border && uv.x < 1.0 - border && uv.y > border && uv.y < 1.0 - border) {\n // Slightly lighter in center\n float cx = (uv.x - 0.5) * 2.0;\n float cy = (uv.y - 0.5) * 2.0;\n float dist = 1.0 - length(vec2(cx, cy)) * 0.1;\n color = baseColor * dist;\n \n // Top highlight\n if (uv.y < border * 2.0) {\n color = baseColor + highlight;\n }\n // Left highlight\n if (uv.x < border * 2.0) {\n color = baseColor + highlight;\n }\n // Bottom shadow\n if (uv.y > 1.0 - border * 2.0) {\n color = baseColor - shadow;\n }\n // Right shadow\n if (uv.x > 1.0 - border * 2.0) {\n color = baseColor - shadow;\n }\n } else {\n // Border area - dark\n color = baseColor * 0.3;\n }\n \n gl_FragColor = vec4(color, v_color.a);\n}\n```\n\nActually, let me simplify this a bit. I'll make it look good but keep the shader manageable.\n\nLet me now write all the code.\n\nI think I should create:\n1. `index.html` - with inline styles and canvas\n2. `src/game.ts` - TypeScript source\n3. `tsconfig.json` - config\n\nThen compile with `npx tsc` or `tsc` if available.\n\nLet me check what's available first, then write the code.\n\nActually, I'll just create all the files and then try to compile. Let me start.\n\nLet me think about the structure of game.ts more carefully:\n\n```typescript\n// ============ CONSTANTS ============\nconst COLS = 10;\nconst ROWS = 20;\nconst BUFFER_ROWS = 4; // hidden rows above\n\n// ============ TYPES ============\ntype Board = number[][]; // 0 = empty, 1-7 = piece color index\ntype Shape = number[][]; // 0 or 1\n\ninterface PieceState {\n type: number; // 0-6 for I,O,T,S,Z,J,L\n shape: Shape;\n x: number;\n y: number;\n}\n\n// ============ PIECE DEFINITIONS ============\nconst PIECE_SHAPES: Shape[] = [\n // I\n [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],\n // O\n [[1,1],[1,1]],\n // T\n [[0,1,0],[1,1,1],[0,0,0]],\n // S\n [[0,1,1],[1,1,0],[0,0,0]],\n // Z\n [[1,1,0],[0,1,1],[0,0,0]],\n // J\n [[1,0,0],[1,1,1],[0,0,0]],\n // L\n [[0,0,1],[1,1,1],[0,0,0]],\n];\n\nconst PIECE_COLORS: [number, number, number][] = [\n [0, 240, 240], // I - cyan\n [240, 240, 0], // O - yellow\n [160, 0, 240], // T - purple\n [0, 240, 0], // S - green\n [240, 0, 0], // Z - red\n [0, 0, 240], // J - blue\n [240, 160, 0], // L - orange\n];\n\n// ============ ROTATION ============\nfunction rotateCW(shape: Shape): Shape { ... }\nfunction rotateCCW(shape: Shape): Shape { ... }\n\n// ============ GAME STATE ============\nlet board: Board;\nlet currentPiece: PieceState | null;\nlet nextPieceType: number;\nlet score: number;\nlet level: number;\nlet lines: number;\nlet gameOver: boolean;\nlet paused: boolean;\nlet dropTimer: number;\nlet lastTime: number;\n// ... etc\n\n// ============ WEBGL RENDERER ============\nclass Renderer {\n gl: WebGLRenderingContext;\n program: WebGLProgram;\n posBuffer: WebGLBuffer;\n // ...\n \n init(canvas: HTMLCanvasElement) { ... }\n drawBoard() { ... }\n drawPiece() { ... }\n drawGhost() { ... }\n drawNext() { ... }\n drawUI() { ... }\n render() { ... }\n}\n\n// ============ GAME LOGIC ============\nfunction initGame() { ... }\nfunction spawnPiece() { ... }\nfunction movePiece(dx: number, dy: number): boolean { ... }\nfunction rotatePiece(dir: number) { ... }\nfunction hardDrop() { ... }\nfunction lockPiece() { ... }\nfunction clearLines() { ... }\nfunction getGhostY(): number { ... }\nfunction collides(shape: Shape, x: number, y: number): boolean { ... }\n\n// ============ INPUT ============\nfunction setupInput() { ... }\n\n// ============ GAME LOOP ============\nfunction gameLoop(time: number) { ... }\nfunction startGame() { ... }\n```\n\nOK, let me write the full implementation now. I'll aim for around 600-800 lines of TypeScript.\n\nLet me also think about the sidebar rendering with WebGL. I need to render:\n- Score text\n- Level text\n- Lines text\n- Next piece preview\n- Game over text\n\nFor text rendering in WebGL, I have a few options:\n1. Use an HTML overlay on top of the canvas (easiest, looks good)\n2. Render text using a texture atlas\n3. Use bitmap font rendering\n\nI'll go with option 1 - use HTML elements for text and the canvas for the game board. This is the most practical approach.\n\nSo the layout will be:\n- Canvas for the game board (WebGL rendered)\n- HTML elements for score, level, next piece, controls info\n- Styled with CSS\n\nThis way I get nice WebGL rendered blocks and clean HTML text.\n\nFor the next piece preview, I could either:\n1. Render it in a separate small canvas\n2. Render it in the main canvas\n3. Use HTML/CSS divs\n\nI'll render it in a separate small canvas for consistency.\n\nActually, let me just render everything in one canvas using WebGL, including the next piece preview area. For text, I'll overlay HTML. This is the cleanest approach.\n\nCanvas layout:\n- Left portion: game board (10 cells wide × 20 cells tall)\n- Right portion: next piece preview, then leave the rest to HTML overlay\n\nHmm, actually this gets complicated with coordinate mapping. Let me keep it simple:\n\n- Main canvas: just the game board\n- HTML: everything else (score, level, lines, next piece as a mini canvas, controls)\n\nLet me use two canvases:\n1. Main game board canvas (WebGL)\n2. Next piece preview canvas (WebGL)\n\nAnd HTML for score/level/lines/controls info.\n\nOK, here's my final plan. Let me write the code.\n\nActually, let me reconsider. I can render the next piece in the main canvas by extending the viewport. The main canvas can be wider and I'll draw the next piece in the extra space. But this complicates things.\n\nSimplest approach:\n1. One main canvas for the board (WebGL) - takes up center of screen\n2. HTML elements for score, next piece (could be a small separate canvas), controls\n3. CSS to lay everything out nicely\n\nLet me go with this. I'll create:\n- `index.html` with layout, CSS, canvas elements, and score/info divs\n- `src/game.ts` with all game logic and WebGL rendering\n\nLet me write it all now. Here goes...\n\nFor the WebGL block rendering, I want to make it look really nice with a beveled 3D effect. Here's my refined shader approach:\n\nEach block vertex will have:\n- `a_position`: vec2 - clip space position\n- `a_localPos`: vec2 - local position within the block (0 to 1)\n- `a_color`: vec3 - base color of the block\n- `a_alpha`: float - alpha value\n\nVertex shader passes localPos and color to fragment shader.\n\nFragment shader computes the final color based on localPos:\n- Border area (outer edge): darker\n- Top-left inner edge: lighter (highlight)\n- Bottom-right inner edge: darker (shadow)\n- Center: base color\n\nThis will give a nice 3D beveled look.\n\nFor the ghost piece, I'll use lower alpha.\n\nFor grid lines, I'll draw them as thin rectangles.\n\nOK, let me write the complete code now. I'll be thorough.\n\nLet me think about the exact WebGL setup:\n\n```\nVertex Shader:\nattribute vec2 a_position; // clip space (-1 to 1)\nattribute vec2 a_localPos; // 0 to 1 within block\nattribute vec3 a_color; // RGB base color\nattribute float a_alpha; // opacity\n\nvarying vec2 v_localPos;\nvarying vec3 v_color;\nvarying float v_alpha;\n\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_localPos = a_localPos;\n v_color = a_color;\n v_alpha = a_alpha;\n}\n\nFragment Shader:\nprecision mediump float;\nvarying vec2 v_localPos;\nvarying vec3 v_color;\nvarying float v_alpha;\n\nvoid main() {\n vec2 uv = v_localPos;\n float border = 0.06;\n float inner = 0.15;\n \n // Black border\n if (uv.x < border || uv.x > 1.0 - border || uv.y < border || uv.y > 1.0 - border) {\n gl_FragColor = vec4(0.0, 0.0, 0.0, v_alpha);\n return;\n }\n \n vec3 color = v_color;\n \n // Top-left highlight\n if (uv.y < inner || uv.x < inner) {\n color = min(color * 1.5, vec3(1.0));\n }\n // Bottom-right shadow\n if (uv.y > 1.0 - inner || uv.x > 1.0 - inner) {\n color = color * 0.5;\n }\n \n gl_FragColor = vec4(color, v_alpha);\n}\n```\n\nThis should give nice-looking blocks.\n\nNow let me write the full code. I'll be comprehensive.\n\nActually, for the next piece canvas, let me just use a second WebGL context or use the same approach. Or... I could draw the next piece using regular 2D canvas since it's simpler. But for consistency, let me use WebGL for both.\n\nOr even simpler: I'll draw the next piece preview in the main canvas by reserving some space on the right side. Let me think...\n\nThe board is 10 × 20 cells. If each cell is 30px, the board is 300 × 600 pixels. I can make the canvas 450 × 600 and use the right 150px for the next piece preview. This way I only need one WebGL context and one set of shaders.\n\nYes, let me do this. One canvas, one WebGL context:\n- Left side (0 to 300px): game board\n- Right side (300 to 450px): next piece preview area\n\nActually, I'll make it look nicer by having a gap between the board and the preview. And the score/level/lines will be HTML overlaid.\n\nLet me finalize the layout:\n\nCanvas: 420 × 660 pixels\n- Board area: x=10 to 310 (300px = 10 cols × 30px), y=30 to 630 (600px = 20 rows × 30px)\n- Next piece area: x=320 to 410 (90px = 3 cells × 30px), y=30 to 150\n\nHmm, this is getting fiddly. Let me just use HTML for all UI elements and keep the canvas purely for the game board. Score, next piece, level, lines will all be HTML elements positioned around the canvas. For the next piece, I'll render it using CSS grid/divs with colored cells. This is much simpler and still looks great.\n\nFinal approach:\n1. Canvas: purely the game board, 300 × 600 pixels (10×20 cells at 30px each)\n2. HTML: score, level, lines, next piece (as a 4×4 grid of colored divs), controls\n3. WebGL: renders only the game board blocks and grid\n\nThis is clean and maintainable. Let me write it now.\n\nActually wait, I want to make the cell size responsive. Let me use a canvas that adapts to screen size but maintains the 1:2 aspect ratio.\n\nOK, I think I'm overthinking this. Let me just write the code with a fixed cell size and reasonable canvas dimensions, and it'll look fine on most screens. I can add some CSS to center everything.\n\nLet me set cell size to 32px. Board = 320 × 640. That's a good size.\n\nHere's my final file structure:\n\n```\ntetris/\n├── index.html # HTML with canvas, UI elements, CSS\n├── src/\n│ └── game.ts # All TypeScript code\n├── tsconfig.json # TypeScript config\n└── (game.js generated by tsc)\n```\n\nLet me write index.html first, then game.ts, then tsconfig.json.\n\nFor the next piece display in HTML, I'll use a 4×4 grid of divs that get colored based on the piece shape.\n\nOK, let me write the actual code now. No more planning.\n\nOne more thought: for WebGL, I need to handle the coordinate mapping from pixel space to clip space:\nclipX = (pixelX / canvasWidth) * 2 - 1\nclipY = 1 - (pixelY / canvasHeight) * 2 (because Y is flipped)\n\nThis is straightforward.\n\nAnd I'll use gl.enable(gl.BLEND) with gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) for transparency support (ghost piece).\n\nOK here we go. Let me write all the code.\n\nI'll structure game.ts with clear sections:\n1. Constants and types\n2. Piece definitions\n3. WebGL renderer class\n4. Game state and logic\n5. Input handling\n6. Main game loop\n\nLet me aim for clean, well-commented code.\n\nOne more thing about the rendering. For the background grid, I'll draw subtle lines. I can do this by drawing thin quads (1-2px wide) in a dark gray color.\n\nActually, for the grid, I can draw the entire background first (dark color), then draw the grid lines on top, then draw the placed blocks, then the ghost piece, then the current piece.\n\nRender order:\n1. Clear canvas with dark background\n2. Draw grid lines (subtle)\n3. Draw placed blocks on the board\n4. Draw ghost piece (semi-transparent)\n5. Draw current piece\n\nI'll batch all the quads into one vertex buffer and draw them all at once. I need to make sure the draw order is correct (back to front).\n\nActually, with blending enabled, I need to draw opaque things first, then transparent things. So:\n1. Draw board background and grid (opaque)\n2. Draw placed blocks (opaque)\n3. Draw current piece (opaque)\n4. Draw ghost piece (semi-transparent)\n\nI can batch steps 1-3 into one draw call (they're all opaque), then draw the ghost piece separately.\n\nOr I can just draw everything in order and it'll look fine since the ghost piece is the only transparent thing and it's drawn last.\n\nLet me just put everything in one vertex buffer in the correct order and draw once. The ghost piece will be drawn last (highest in the buffer), and since I'm using blending, it'll blend correctly over whatever is behind it.\n\nWait, actually that's not how blending works. Blending uses the current framebuffer content as the destination. So if I draw everything in one draw call, the blending happens against the clear color, not against the blocks behind the ghost. That's wrong.\n\nSo I need two draw calls:\n1. Draw everything opaque (background, grid, placed blocks, current piece)\n2. Draw ghost piece (transparent)\n\nOr I could just draw the ghost piece with a solid but darker color instead of transparency. That avoids the blending issue.\n\nLet me do this: draw the ghost piece as a slightly dimmed version of the piece color with alpha 0.3. I'll use two draw calls: one for opaque elements, one for the ghost.\n\nOK, now I'm really writing the code.\n\nLet me also add DAS (Delayed Auto Shift) for smooth movement when holding left/right keys. This makes the game feel much better.\n\nDAS parameters:\n- Delay before auto-repeat: 170ms\n- Auto-repeat rate: 50ms (20 moves per second)\n\nI'll track key states and timestamps.\n\nAnd for soft drop, holding down arrow should move the piece down at a faster rate (maybe every 50ms).\n\nOK, writing the code now...\n\nLet me think about the total structure one more time. I'll write it all in one big TypeScript file with clear sections. No classes - just functions and module-level state. This keeps it simple for a single-file game.\n\nActually, I'll use a Renderer class for the WebGL stuff and a namespace/module for the game state. Or just keep everything as module-level. Let me just write it procedurally.\n\nHere's my final structure for game.ts:\n\n```typescript\n// ===== CONSTANTS =====\n// ===== TYPES =====\n// ===== PIECE DATA =====\n// ===== GAME STATE =====\n// ===== WEBGL SETUP =====\n// ===== RENDERING =====\n// ===== GAME LOGIC =====\n// ===== INPUT =====\n// ===== MAIN =====\n```\n\nLet me write it now. For real this time.\n\nI realize I should also handle the \"bag\" randomizer used in modern Tetris (all 7 pieces appear once before repeating, shuffled). This prevents long droughts of certain pieces.\n\n7-bag randomizer:\n1. Create an array [0,1,2,3,4,5,6]\n2. Shuffle it\n3. Deal pieces from this bag\n4. When bag is empty, create a new shuffled bag\n\nThis is the standard modern Tetris randomizer and makes the game much fairer.\n\nOK, let me write the actual code now. I'll create comprehensive, working code.\n\nHere's my complete implementation:\n\n```typescript\n// ===== CONSTANTS =====\nconst COLS = 10;\nconst ROWS = 20;\nconst CELL_SIZE = 32;\nconst BOARD_X = 0;\nconst BOARD_Y = 0;\nconst CANVAS_WIDTH = COLS * CELL_SIZE;\nconst CANVAS_HEIGHT = ROWS * CELL_SIZE;\n\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\nconst SOFT_DROP_RATE = 50; // ms between soft drops\nconst LOCK_DELAY = 500; // ms before piece locks (could implement this)\n\n// ===== PIECE DEFINITIONS =====\nconst SHAPES: number[][][] = [\n // I piece\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0]\n ],\n // O piece\n [\n [1, 1],\n [1, 1]\n ],\n // T piece\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0]\n ],\n // S piece\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0]\n ],\n // Z piece\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0]\n ],\n // J piece\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0]\n ],\n // L piece\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0]\n ]\n];\n\n// Colors as [R, G, B] normalized to 0-1\nconst COLORS: [number, number, number][] = [\n [0.0, 0.9, 0.9], // I - cyan\n [0.9, 0.9, 0.0], // O - yellow\n [0.6, 0.0, 0.9], // T - purple\n [0.0, 0.9, 0.0], // S - green\n [0.9, 0.0, 0.0], // Z - red\n [0.0, 0.0, 0.9], // J - blue\n [0.9, 0.6, 0.0], // L - orange\n];\n\n// ===== TYPES =====\ninterface Piece {\n type: number; // 0-6\n shape: number[][]; // current rotation state\n x: number; // board column position\n y: number; // board row position\n}\n\n// ===== GAME STATE =====\nlet board: number[][] = [];\nlet currentPiece: Piece | null = null;\nlet nextPieceType: number = -1;\nlet holdPieceType: number = -1;\nlet canHold: boolean = true;\nlet score: number = 0;\nlet level: number = 1;\nlet lines: number = 0;\nlet combo: number = 0;\nlet gameOver: boolean = false;\nlet paused: boolean = false;\nlet bag: number[] = [];\nlet dropInterval: number = 1000;\nlet dropTimer: number = 0;\nlet lastFrameTime: number = 0;\nlet animFrameId: number = ","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"fa481450-8b03-43bf-bdc6-a7d7f99e6f2b","uuid":"6b32a4cd-beeb-4f49-9481-59334f86bcf4"} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/claude_stderr.log b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/claude_stderr.log diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/eval_results.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/eval_results.json @@ -0,0 +1,134 @@ +{ + "structural": { + "pass": false, + "checks": [ + { + "name": "entry_point_exists", + "pass": false, + "detail": "no index.html found in workspace root, dist/, or public/" + }, + { + "name": "package_json_exists", + "pass": true, + "detail": "package.json found" + }, + { + "name": "build_succeeds", + "pass": true, + "detail": "no build script defined (static project)" + }, + { + "name": "typescript_compiles", + "pass": false, + "detail": "TypeScript files found but no tsconfig.json" + } + ], + "score": 0.5 + }, + "quality": { + "lint": { + "pass": true, + "errors": 0, + "warnings": 0 + }, + "typecheck": { + "pass": false, + "error": "no tsconfig.json" + }, + "performance": { + "pass": true, + "bundle_size_bytes": 0, + "size_under_512kb": true + }, + "score": 0.67 + }, + "code_analysis": { + "files": { + "total": 6, + "code": 4, + "docs": 0, + "unnecessary": 0, + "unnecessary_list": [] + }, + "lines_of_code": 596, + "dependencies": { + "production": 0, + "dev": 4, + "total": 4 + }, + "complexity": "moderate", + "console_logs": 0, + "magic_numbers": { + "count": 37, + "excessive": true + }, + "function_length": { + "count": 16, + "average": 6.6, + "max": 16, + "long_functions": 0 + }, + "max_nesting_depth": 5, + "global_declarations": 0, + "naming": { + "dominant_style": "camelCase", + "consistency_pct": 100.0, + "camel_case": 141, + "snake_case": 0 + }, + "error_handling": { + "try_catch_blocks": 4, + "has_error_handling": true + }, + "comments": { + "comment_lines": 70, + "source_lines": 444, + "ratio_pct": 15.8 + }, + "separation_of_concerns": { + "verdict": "mixed", + "files_with_rendering": 2, + "files_with_logic": 1, + "files_with_both": 1 + }, + "html_validation": { + "valid": false, + "errors": 0 + }, + "duplication_percentage": 0.0, + "score": 0.55 + }, + "transcript_analysis": { + "total_events": 4, + "tool_calls": { + "total": 0, + "bash": 0, + "write": 0, + "edit": 0, + "read": 0 + }, + "wasted_turns": { + "total": 0, + "docs": 0, + "ascii_art": 0, + "server_starts": 0 + }, + "errors_encountered": 0, + "thinking_blocks": 1, + "text_blocks": 0, + "productivity_ratio": 0, + "self_tested": false, + "score": 1.0 + }, + "gameplay_bot": { + "pass": false, + "score": 0, + "error": "no HTML files in workspace - game was not built" + }, + "outcome_score": 0.0, + "score": 0.0, + "sonarqube": { + "error": "SonarQube not running at localhost:9000", + "score": 0 + } +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/meta.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/meta.json @@ -33,5 +33,8 @@ "short_cell_id": "cd8ad131", "run_number": 1, "claude_version": "2.1.92 (Claude Code)", - "started_at": "2026-04-07T01:50:51.338095+00:00" + "started_at": "2026-04-07T01:50:51.338095+00:00", + "wall_time_seconds": 1200, + "exit_code": 124, + "completed_at": "2026-04-07T02:10:52.798576+00:00" } \ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/transcript.jsonl b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run1/transcript.jsonl @@ -0,0 +1,4 @@ +{"type": "harness", "subtype": "config", "model": "glm-5.1", "effort": "high", "tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebSearch", "WebFetch"], "max_budget_usd": 2.0, "timeout_seconds": 1200, "task": "tetris", "language": "typescript", "prompt_style": "simple"} +{"type": "user", "subtype": "prompt", "message": {"role": "user", "content": "Build a playable Tetris game that runs in a browser. It should have all the standard mechanics: piece rotation, line clearing, scoring, and increasing speed. Use keyboard controls for movement and rotation.\n\n\nUse TypeScript.\n\nRender the game using WebGL."}} +{"type":"system","subtype":"init","cwd":"/tmp/loop-bench-l7ah2axi","session_id":"fa481450-8b03-43bf-bdc6-a7d7f99e6f2b","tools":["Bash","Edit","Read"],"mcp_servers":[],"model":"glm-5.1","permissionMode":"dontAsk","slash_commands":["update-config","debug","simplify","batch","loop","schedule","claude-api","compact","context","cost","heapdump","init","review","security-review","insights"],"apiKeySource":"apiKeyHelper","claude_code_version":"2.1.92","output_style":"default","agents":["general-purpose","statusline-setup","Explore","Plan"],"skills":["update-config","debug","simplify","batch","loop","schedule","claude-api"],"plugins":[{"name":"claude-hud","path":"/root/.claude/plugins/marketplaces/claude-hud/","source":"claude-hud@claude-hud"},{"name":"rust-analyzer-lsp","path":"/root/.claude/plugins/marketplaces/claude-plugins-official/plugins/rust-analyzer-lsp","source":"rust-analyzer-lsp@claude-plugins-official"}],"uuid":"7abcf9ad-6f3f-4c57-a53a-6216f0536599","fast_mode_state":"off"} +{"type":"assistant","message":{"id":"msg_20260407095053507b00bf35da40ed","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The user wants me to build a playable Tetris game that runs in a browser with:\n- Standard Tetris mechanics (piece rotation, line clearing, scoring, increasing speed)\n- Keyboard controls\n- TypeScript\n- WebGL rendering\n\nLet me plan this out:\n\n1. I'll create a single HTML file that contains the TypeScript (compiled to JS) or I could set up a proper TypeScript project. Since they want TypeScript, let me create a proper project structure.\n\n2. Actually, for simplicity and to make it runnable directly, I'll create an HTML file and a TypeScript file, then compile the TypeScript. But let me think about the best approach.\n\nLet me create:\n- A project directory with `index.html`\n- A `game.ts` file with all the game logic and WebGL rendering\n- A `tsconfig.json` for TypeScript compilation\n- Use a simple build approach\n\nActually, the simplest approach that still uses TypeScript properly:\n1. Create `index.html` with the canvas and UI\n2. Create `game.ts` with all game logic and WebGL rendering\n3. Compile with `tsc`\n4. Serve or open directly\n\nLet me design the game:\n\n**Tetris pieces (Tetrominoes):**\n- I, O, T, S, Z, J, L\n\n**Game mechanics:**\n- 10 columns × 20 rows grid\n- Pieces fall from top\n- Rotation (SRS - Super Rotation System or simple rotation)\n- Line clearing with scoring\n- Speed increases with level\n- Game over when pieces stack to top\n\n**Scoring:**\n- Single: 100 × level\n- Double: 300 × level\n- Triple: 500 × level\n- Tetris: 800 × level\n\n**WebGL Rendering:**\n- Draw colored quads for each cell\n- Use shaders to render blocks with nice colors and borders\n\n**Controls:**\n- Left/Right arrows: move\n- Up arrow: rotate\n- Down arrow: soft drop\n- Space: hard drop\n\nLet me write this out. I'll create a comprehensive implementation.\n\nFor WebGL, I'll:\n1. Create a simple 2D shader program\n2. Draw each block as a colored rectangle\n3. Add grid lines\n4. Add glow/border effects\n\nLet me think about the WebGL approach more carefully:\n- I'll use a simple vertex/fragment shader pair\n- Each block will be drawn as two triangles (a quad)\n- I'll pass color and position as uniforms or attributes\n- For efficiency, I can batch all quads into a single draw call\n\nLet me structure the TypeScript code:\n\n```typescript\n// Types and constants\n// WebGL setup\n// Game state management\n// Piece definitions and rotation\n// Game loop\n// Input handling\n// Rendering\n// Scoring\n```\n\nLet me write the complete implementation now.\n\nI'll create:\n1. `index.html` - HTML structure with canvas, score display, etc.\n2. `src/game.ts` - Main game code in TypeScript\n3. `tsconfig.json` - TypeScript config\n4. `package.json` - For dependencies\n\nActually, to keep it simple and self-contained, let me create just the HTML and TS files, with the TS compiled to JS that the HTML loads.\n\nLet me think about the WebGL rendering approach more carefully:\n\nI'll use instanced rendering or just batched vertex buffers. For simplicity, I'll build a vertex buffer each frame with all the quads.\n\nEach quad = 2 triangles = 6 vertices. Each vertex has position (x, y) and color (r, g, b).\n\nFor the grid (10×20), plus current piece, plus ghost piece, plus next piece preview, this is very manageable.\n\nLet me write the shaders:\n\nVertex shader:\n```glsl\nattribute vec2 a_position;\nattribute vec4 a_color;\nvarying vec4 v_color;\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_color = a_color;\n}\n```\n\nFragment shader:\n```glsl\nprecision mediump float;\nvarying vec4 v_color;\nvoid main() {\n gl_FragColor = v_color;\n}\n```\n\nFor nice-looking blocks, I can add a slight border/3D effect by drawing each block as a slightly inset colored rect with a darker border.\n\nActually, I can make it look nicer by passing texture coordinates and computing the border in the fragment shader.\n\nLet me revise - I'll pass position, color, and a \"local\" coordinate (0-1 within each block) so I can compute borders and shading in the fragment shader.\n\nEach vertex: x, y, u, v, r, g, b, a\nWhere u,v are 0-1 local coordinates within the block.\n\nFragment shader can then:\n- Darken edges for a 3D effect\n- Add a highlight on top-left\n- Add shadow on bottom-right\n\nThis will make it look much nicer than flat colored blocks.\n\nLet me plan the full implementation:\n\n```\nBoard: 10 wide × 20 tall (+ 2 invisible rows at top for spawning)\nCell size: will be calculated to fit the canvas\n\nPieces:\n I: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]]\n O: [[1,1],[1,1]]\n T: [[0,1,0],[1,1,1],[0,0,0]]\n S: [[0,1,1],[1,1,0],[0,0,0]]\n Z: [[1,1,0],[0,1,1],[0,0,0]]\n J: [[1,0,0],[1,1,1],[0,0,0]]\n L: [[0,0,1],[1,1,1],[0,0,0]]\n\nColors:\n I: cyan\n O: yellow\n T: purple\n S: green\n Z: red\n J: blue\n L: orange\n\nRotation: Simple clockwise/counter-clockwise rotation of the matrix\n\nGame loop:\n- Every tick (speed based on level), move piece down\n- If can't move down, lock piece\n- Check for completed lines\n- Update score\n- Spawn new piece\n- If can't spawn, game over\n\nLevel increases every 10 lines cleared.\nSpeed formula: frames per tick decreasing with level\n```\n\nAlright, let me write the code. I'll create a single TypeScript file with everything.\n\nFor the project setup:\n- No npm dependencies needed (pure TypeScript/JavaScript)\n- Just need TypeScript compiler\n\nLet me create the files:\n\n1. First, let me check what's available in the environment.\n\nActually, let me just start creating the files. I'll make a clean project structure.\n\nLet me think about the canvas sizing:\n- Board: 10 × 20 cells\n- I'll add a sidebar for score, next piece, level\n- Canvas could be something like 400×800 for the board area\n\nActually, let me make the canvas fill the center of the screen and calculate cell sizes dynamically.\n\nLet me write this now. I'll create:\n- `/tmp/loop-bench-l7ah2axi/tetris/index.html`\n- `/tmp/loop-bench-l7ah2axi/tetris/tsconfig.json`\n- `/tmp/loop-bench-l7ah2axi/tetris/src/game.ts`\n\nThen compile and test.\n\nLet me think about the WebGL rendering more carefully. I want nice-looking blocks.\n\nFor each filled cell, I'll draw a quad with:\n- A base color (the piece color)\n- A lighter highlight on the top and left edges (3D effect)\n- A darker shadow on the bottom and right edges\n\nI can achieve this by passing local UV coordinates (0 to 1 within each block) and computing the shading in the fragment shader.\n\nFor the background grid, I'll draw subtle lines.\n\nLet me write the vertex format:\n- position: vec2 (screen coordinates in clip space)\n- localUV: vec2 (0-1 within the block for shading)\n- color: vec4 (base color)\n\nTotal: 8 floats per vertex, 6 vertices per quad = 48 floats per block.\n\nFor a full board (200 cells max) + current piece (4 cells) + ghost (4 cells) + grid lines, this is very small.\n\nLet me code this up properly.\n\nActually, I realize I should think about the coordinate system. In WebGL, clip space goes from -1 to 1. I'll map the game board to clip space.\n\nBoard layout:\n- 10 columns × 20 rows\n- Cell size in clip space: I'll calculate based on canvas aspect ratio\n\nActually, it's easier to work in pixel coordinates and use a projection matrix or just convert to clip space.\n\nLet me use a simpler approach: work in \"game coordinates\" where (0,0) is top-left of the board, and convert to clip space in the vertex shader using uniforms for the board dimensions.\n\nHmm, actually the simplest approach for this game:\n1. Define board dimensions in pixels\n2. Convert pixel positions to clip space: clipX = (pixelX / canvasWidth) * 2 - 1, clipY = 1 - (pixelY / canvasHeight) * 2\n\nOr I can pass a projection uniform to the shader.\n\nLet me keep it simple: I'll calculate clip-space coordinates directly in JavaScript and pass them as vertex attributes.\n\nOK let me just write the code now. I've been planning long enough.\n\nLet me structure the TypeScript code with classes:\n\n```typescript\n// Constants\nconst BOARD_WIDTH = 10;\nconst BOARD_HEIGHT = 20;\nconst HIDDEN_ROWS = 2; // extra rows above visible area for spawning\n\n// Piece types\nenum PieceType { I, O, T, S, Z, J, L }\n\n// Piece definitions (rotation states)\n// Colors\n\nclass Piece {\n type: PieceType;\n x: number;\n y: number;\n rotation: number;\n shape: number[][];\n}\n\nclass Game {\n board: number[][]; // 0 = empty, 1-7 = piece type+1\n currentPiece: Piece | null;\n nextPiece: Piece | null;\n score: number;\n level: number;\n linesCleared: number;\n gameOver: boolean;\n // ...\n}\n\nclass Renderer {\n gl: WebGLRenderingContext;\n program: WebGLProgram;\n // ...\n}\n\n// Input handler\n// Game loop\n```\n\nLet me write the complete code now. I'll aim for clean, well-structured TypeScript with good WebGL rendering.\n\nHere's my plan for the files:\n\n**index.html**: Basic HTML with canvas, score display, controls info\n**src/game.ts**: All game logic and rendering in one file\n\nLet me start coding. I'll write a comprehensive implementation.\n\nFor the piece rotation, I'll use a simple matrix rotation (transpose + reverse for clockwise).\n\nFor wall kicks, I'll implement basic wall kicks (try offsets when rotation would cause collision).\n\nLet me think about the exact pixel layout:\n- Canvas: let's say 480 × 640 pixels\n- Board area: 300 × 600 pixels (30px per cell), positioned on the left\n- Sidebar: remaining width on the right for score, next piece, level\n\nActually, I'll make it responsive. Let me use the full viewport and calculate sizes dynamically.\n\nLet me just write the code now. Here's my full implementation plan:\n\n```\nTetromino shapes as 2D arrays for each rotation state\nOR compute rotation from a single shape definition\n\nI'll define each piece as a 2D boolean/int array and compute rotations.\n\nPiece colors:\nI = cyan (#00FFFF)\nO = yellow (#FFFF00)\nT = purple (#800080) or (#AA00FF)\nS = green (#00FF00)\nZ = red (#FF0000)\nJ = blue (#0000FF)\nL = orange (#FF8000)\n\nBoard representation:\n- 2D array, 0 = empty, 1-7 = filled (piece type + 1)\n\nGhost piece: show where the piece will land (semi-transparent)\n\nNext piece preview: show the next piece in the sidebar\n\nScoring:\n- Soft drop: 1 point per cell\n- Hard drop: 2 points per cell\n- Single line: 100 × level\n- Double: 300 × level\n- Triple: 500 × level\n- Tetris: 800 × level\n\nLevel up every 10 lines.\n\nSpeed:\n- Level 1: 1000ms per tick\n- Level 2: 900ms\n- ...\n- Level 10: 100ms\n- Formula: Math.max(100, 1000 - (level - 1) * 100)\n- Or use the NES formula: frames per row\n\nI'll use: delay = Math.max(50, 1000 * Math.pow(0.85, level - 1))\n\nThis gives:\nLevel 1: 1000ms\nLevel 2: 850ms\nLevel 3: 722ms\nLevel 5: 522ms\nLevel 10: 232ms\nLevel 15: 103ms\nLevel 20: 46ms (capped at 50)\n\nThat feels about right.\n\nControls:\n- Left arrow: move left\n- Right arrow: move right\n- Up arrow or X: rotate clockwise\n- Z: rotate counter-clockwise\n- Down arrow: soft drop\n- Space: hard drop\n- P: pause\n```\n\nOK, now let me actually write the code. I'll create a comprehensive, well-structured game.\n\nFor the WebGL rendering, I want to make it look really nice:\n\n1. Background: dark gray/black with subtle grid lines\n2. Placed blocks: solid colors with 3D beveled edge effect\n3. Current piece: same as placed blocks\n4. Ghost piece: semi-transparent version of current piece color\n5. Next piece preview: in the sidebar\n6. Border around the playing field\n\nFor the 3D beveled effect on blocks, I'll use the local UV coordinates:\n- Top edge: lighter\n- Left edge: lighter\n- Bottom edge: darker\n- Right edge: darker\n- Center: base color\n- Small border around each block\n\nHere's the fragment shader approach:\n\n```glsl\nprecision mediump float;\nvarying vec2 v_localUV;\nvarying vec4 v_color;\nvarying float v_isBlock;\n\nvoid main() {\n if (v_isBlock < 0.5) {\n // Grid line or background\n gl_FragColor = v_color;\n return;\n }\n \n vec2 uv = v_localUV;\n float border = 0.08;\n float highlight = 0.3;\n float shadow = 0.3;\n \n vec3 baseColor = v_color.rgb;\n vec3 color = baseColor;\n \n // Inner area\n if (uv.x > border && uv.x < 1.0 - border && uv.y > border && uv.y < 1.0 - border) {\n // Slightly lighter in center\n float cx = (uv.x - 0.5) * 2.0;\n float cy = (uv.y - 0.5) * 2.0;\n float dist = 1.0 - length(vec2(cx, cy)) * 0.1;\n color = baseColor * dist;\n \n // Top highlight\n if (uv.y < border * 2.0) {\n color = baseColor + highlight;\n }\n // Left highlight\n if (uv.x < border * 2.0) {\n color = baseColor + highlight;\n }\n // Bottom shadow\n if (uv.y > 1.0 - border * 2.0) {\n color = baseColor - shadow;\n }\n // Right shadow\n if (uv.x > 1.0 - border * 2.0) {\n color = baseColor - shadow;\n }\n } else {\n // Border area - dark\n color = baseColor * 0.3;\n }\n \n gl_FragColor = vec4(color, v_color.a);\n}\n```\n\nActually, let me simplify this a bit. I'll make it look good but keep the shader manageable.\n\nLet me now write all the code.\n\nI think I should create:\n1. `index.html` - with inline styles and canvas\n2. `src/game.ts` - TypeScript source\n3. `tsconfig.json` - config\n\nThen compile with `npx tsc` or `tsc` if available.\n\nLet me check what's available first, then write the code.\n\nActually, I'll just create all the files and then try to compile. Let me start.\n\nLet me think about the structure of game.ts more carefully:\n\n```typescript\n// ============ CONSTANTS ============\nconst COLS = 10;\nconst ROWS = 20;\nconst BUFFER_ROWS = 4; // hidden rows above\n\n// ============ TYPES ============\ntype Board = number[][]; // 0 = empty, 1-7 = piece color index\ntype Shape = number[][]; // 0 or 1\n\ninterface PieceState {\n type: number; // 0-6 for I,O,T,S,Z,J,L\n shape: Shape;\n x: number;\n y: number;\n}\n\n// ============ PIECE DEFINITIONS ============\nconst PIECE_SHAPES: Shape[] = [\n // I\n [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]],\n // O\n [[1,1],[1,1]],\n // T\n [[0,1,0],[1,1,1],[0,0,0]],\n // S\n [[0,1,1],[1,1,0],[0,0,0]],\n // Z\n [[1,1,0],[0,1,1],[0,0,0]],\n // J\n [[1,0,0],[1,1,1],[0,0,0]],\n // L\n [[0,0,1],[1,1,1],[0,0,0]],\n];\n\nconst PIECE_COLORS: [number, number, number][] = [\n [0, 240, 240], // I - cyan\n [240, 240, 0], // O - yellow\n [160, 0, 240], // T - purple\n [0, 240, 0], // S - green\n [240, 0, 0], // Z - red\n [0, 0, 240], // J - blue\n [240, 160, 0], // L - orange\n];\n\n// ============ ROTATION ============\nfunction rotateCW(shape: Shape): Shape { ... }\nfunction rotateCCW(shape: Shape): Shape { ... }\n\n// ============ GAME STATE ============\nlet board: Board;\nlet currentPiece: PieceState | null;\nlet nextPieceType: number;\nlet score: number;\nlet level: number;\nlet lines: number;\nlet gameOver: boolean;\nlet paused: boolean;\nlet dropTimer: number;\nlet lastTime: number;\n// ... etc\n\n// ============ WEBGL RENDERER ============\nclass Renderer {\n gl: WebGLRenderingContext;\n program: WebGLProgram;\n posBuffer: WebGLBuffer;\n // ...\n \n init(canvas: HTMLCanvasElement) { ... }\n drawBoard() { ... }\n drawPiece() { ... }\n drawGhost() { ... }\n drawNext() { ... }\n drawUI() { ... }\n render() { ... }\n}\n\n// ============ GAME LOGIC ============\nfunction initGame() { ... }\nfunction spawnPiece() { ... }\nfunction movePiece(dx: number, dy: number): boolean { ... }\nfunction rotatePiece(dir: number) { ... }\nfunction hardDrop() { ... }\nfunction lockPiece() { ... }\nfunction clearLines() { ... }\nfunction getGhostY(): number { ... }\nfunction collides(shape: Shape, x: number, y: number): boolean { ... }\n\n// ============ INPUT ============\nfunction setupInput() { ... }\n\n// ============ GAME LOOP ============\nfunction gameLoop(time: number) { ... }\nfunction startGame() { ... }\n```\n\nOK, let me write the full implementation now. I'll aim for around 600-800 lines of TypeScript.\n\nLet me also think about the sidebar rendering with WebGL. I need to render:\n- Score text\n- Level text\n- Lines text\n- Next piece preview\n- Game over text\n\nFor text rendering in WebGL, I have a few options:\n1. Use an HTML overlay on top of the canvas (easiest, looks good)\n2. Render text using a texture atlas\n3. Use bitmap font rendering\n\nI'll go with option 1 - use HTML elements for text and the canvas for the game board. This is the most practical approach.\n\nSo the layout will be:\n- Canvas for the game board (WebGL rendered)\n- HTML elements for score, level, next piece, controls info\n- Styled with CSS\n\nThis way I get nice WebGL rendered blocks and clean HTML text.\n\nFor the next piece preview, I could either:\n1. Render it in a separate small canvas\n2. Render it in the main canvas\n3. Use HTML/CSS divs\n\nI'll render it in a separate small canvas for consistency.\n\nActually, let me just render everything in one canvas using WebGL, including the next piece preview area. For text, I'll overlay HTML. This is the cleanest approach.\n\nCanvas layout:\n- Left portion: game board (10 cells wide × 20 cells tall)\n- Right portion: next piece preview, then leave the rest to HTML overlay\n\nHmm, actually this gets complicated with coordinate mapping. Let me keep it simple:\n\n- Main canvas: just the game board\n- HTML: everything else (score, level, lines, next piece as a mini canvas, controls)\n\nLet me use two canvases:\n1. Main game board canvas (WebGL)\n2. Next piece preview canvas (WebGL)\n\nAnd HTML for score/level/lines/controls info.\n\nOK, here's my final plan. Let me write the code.\n\nActually, let me reconsider. I can render the next piece in the main canvas by extending the viewport. The main canvas can be wider and I'll draw the next piece in the extra space. But this complicates things.\n\nSimplest approach:\n1. One main canvas for the board (WebGL) - takes up center of screen\n2. HTML elements for score, next piece (could be a small separate canvas), controls\n3. CSS to lay everything out nicely\n\nLet me go with this. I'll create:\n- `index.html` with layout, CSS, canvas elements, and score/info divs\n- `src/game.ts` with all game logic and WebGL rendering\n\nLet me write it all now. Here goes...\n\nFor the WebGL block rendering, I want to make it look really nice with a beveled 3D effect. Here's my refined shader approach:\n\nEach block vertex will have:\n- `a_position`: vec2 - clip space position\n- `a_localPos`: vec2 - local position within the block (0 to 1)\n- `a_color`: vec3 - base color of the block\n- `a_alpha`: float - alpha value\n\nVertex shader passes localPos and color to fragment shader.\n\nFragment shader computes the final color based on localPos:\n- Border area (outer edge): darker\n- Top-left inner edge: lighter (highlight)\n- Bottom-right inner edge: darker (shadow)\n- Center: base color\n\nThis will give a nice 3D beveled look.\n\nFor the ghost piece, I'll use lower alpha.\n\nFor grid lines, I'll draw them as thin rectangles.\n\nOK, let me write the complete code now. I'll be thorough.\n\nLet me think about the exact WebGL setup:\n\n```\nVertex Shader:\nattribute vec2 a_position; // clip space (-1 to 1)\nattribute vec2 a_localPos; // 0 to 1 within block\nattribute vec3 a_color; // RGB base color\nattribute float a_alpha; // opacity\n\nvarying vec2 v_localPos;\nvarying vec3 v_color;\nvarying float v_alpha;\n\nvoid main() {\n gl_Position = vec4(a_position, 0.0, 1.0);\n v_localPos = a_localPos;\n v_color = a_color;\n v_alpha = a_alpha;\n}\n\nFragment Shader:\nprecision mediump float;\nvarying vec2 v_localPos;\nvarying vec3 v_color;\nvarying float v_alpha;\n\nvoid main() {\n vec2 uv = v_localPos;\n float border = 0.06;\n float inner = 0.15;\n \n // Black border\n if (uv.x < border || uv.x > 1.0 - border || uv.y < border || uv.y > 1.0 - border) {\n gl_FragColor = vec4(0.0, 0.0, 0.0, v_alpha);\n return;\n }\n \n vec3 color = v_color;\n \n // Top-left highlight\n if (uv.y < inner || uv.x < inner) {\n color = min(color * 1.5, vec3(1.0));\n }\n // Bottom-right shadow\n if (uv.y > 1.0 - inner || uv.x > 1.0 - inner) {\n color = color * 0.5;\n }\n \n gl_FragColor = vec4(color, v_alpha);\n}\n```\n\nThis should give nice-looking blocks.\n\nNow let me write the full code. I'll be comprehensive.\n\nActually, for the next piece canvas, let me just use a second WebGL context or use the same approach. Or... I could draw the next piece using regular 2D canvas since it's simpler. But for consistency, let me use WebGL for both.\n\nOr even simpler: I'll draw the next piece preview in the main canvas by reserving some space on the right side. Let me think...\n\nThe board is 10 × 20 cells. If each cell is 30px, the board is 300 × 600 pixels. I can make the canvas 450 × 600 and use the right 150px for the next piece preview. This way I only need one WebGL context and one set of shaders.\n\nYes, let me do this. One canvas, one WebGL context:\n- Left side (0 to 300px): game board\n- Right side (300 to 450px): next piece preview area\n\nActually, I'll make it look nicer by having a gap between the board and the preview. And the score/level/lines will be HTML overlaid.\n\nLet me finalize the layout:\n\nCanvas: 420 × 660 pixels\n- Board area: x=10 to 310 (300px = 10 cols × 30px), y=30 to 630 (600px = 20 rows × 30px)\n- Next piece area: x=320 to 410 (90px = 3 cells × 30px), y=30 to 150\n\nHmm, this is getting fiddly. Let me just use HTML for all UI elements and keep the canvas purely for the game board. Score, next piece, level, lines will all be HTML elements positioned around the canvas. For the next piece, I'll render it using CSS grid/divs with colored cells. This is much simpler and still looks great.\n\nFinal approach:\n1. Canvas: purely the game board, 300 × 600 pixels (10×20 cells at 30px each)\n2. HTML: score, level, lines, next piece (as a 4×4 grid of colored divs), controls\n3. WebGL: renders only the game board blocks and grid\n\nThis is clean and maintainable. Let me write it now.\n\nActually wait, I want to make the cell size responsive. Let me use a canvas that adapts to screen size but maintains the 1:2 aspect ratio.\n\nOK, I think I'm overthinking this. Let me just write the code with a fixed cell size and reasonable canvas dimensions, and it'll look fine on most screens. I can add some CSS to center everything.\n\nLet me set cell size to 32px. Board = 320 × 640. That's a good size.\n\nHere's my final file structure:\n\n```\ntetris/\n├── index.html # HTML with canvas, UI elements, CSS\n├── src/\n│ └── game.ts # All TypeScript code\n├── tsconfig.json # TypeScript config\n└── (game.js generated by tsc)\n```\n\nLet me write index.html first, then game.ts, then tsconfig.json.\n\nFor the next piece display in HTML, I'll use a 4×4 grid of divs that get colored based on the piece shape.\n\nOK, let me write the actual code now. No more planning.\n\nOne more thought: for WebGL, I need to handle the coordinate mapping from pixel space to clip space:\nclipX = (pixelX / canvasWidth) * 2 - 1\nclipY = 1 - (pixelY / canvasHeight) * 2 (because Y is flipped)\n\nThis is straightforward.\n\nAnd I'll use gl.enable(gl.BLEND) with gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) for transparency support (ghost piece).\n\nOK here we go. Let me write all the code.\n\nI'll structure game.ts with clear sections:\n1. Constants and types\n2. Piece definitions\n3. WebGL renderer class\n4. Game state and logic\n5. Input handling\n6. Main game loop\n\nLet me aim for clean, well-commented code.\n\nOne more thing about the rendering. For the background grid, I'll draw subtle lines. I can do this by drawing thin quads (1-2px wide) in a dark gray color.\n\nActually, for the grid, I can draw the entire background first (dark color), then draw the grid lines on top, then draw the placed blocks, then the ghost piece, then the current piece.\n\nRender order:\n1. Clear canvas with dark background\n2. Draw grid lines (subtle)\n3. Draw placed blocks on the board\n4. Draw ghost piece (semi-transparent)\n5. Draw current piece\n\nI'll batch all the quads into one vertex buffer and draw them all at once. I need to make sure the draw order is correct (back to front).\n\nActually, with blending enabled, I need to draw opaque things first, then transparent things. So:\n1. Draw board background and grid (opaque)\n2. Draw placed blocks (opaque)\n3. Draw current piece (opaque)\n4. Draw ghost piece (semi-transparent)\n\nI can batch steps 1-3 into one draw call (they're all opaque), then draw the ghost piece separately.\n\nOr I can just draw everything in order and it'll look fine since the ghost piece is the only transparent thing and it's drawn last.\n\nLet me just put everything in one vertex buffer in the correct order and draw once. The ghost piece will be drawn last (highest in the buffer), and since I'm using blending, it'll blend correctly over whatever is behind it.\n\nWait, actually that's not how blending works. Blending uses the current framebuffer content as the destination. So if I draw everything in one draw call, the blending happens against the clear color, not against the blocks behind the ghost. That's wrong.\n\nSo I need two draw calls:\n1. Draw everything opaque (background, grid, placed blocks, current piece)\n2. Draw ghost piece (transparent)\n\nOr I could just draw the ghost piece with a solid but darker color instead of transparency. That avoids the blending issue.\n\nLet me do this: draw the ghost piece as a slightly dimmed version of the piece color with alpha 0.3. I'll use two draw calls: one for opaque elements, one for the ghost.\n\nOK, now I'm really writing the code.\n\nLet me also add DAS (Delayed Auto Shift) for smooth movement when holding left/right keys. This makes the game feel much better.\n\nDAS parameters:\n- Delay before auto-repeat: 170ms\n- Auto-repeat rate: 50ms (20 moves per second)\n\nI'll track key states and timestamps.\n\nAnd for soft drop, holding down arrow should move the piece down at a faster rate (maybe every 50ms).\n\nOK, writing the code now...\n\nLet me think about the total structure one more time. I'll write it all in one big TypeScript file with clear sections. No classes - just functions and module-level state. This keeps it simple for a single-file game.\n\nActually, I'll use a Renderer class for the WebGL stuff and a namespace/module for the game state. Or just keep everything as module-level. Let me just write it procedurally.\n\nHere's my final structure for game.ts:\n\n```typescript\n// ===== CONSTANTS =====\n// ===== TYPES =====\n// ===== PIECE DATA =====\n// ===== GAME STATE =====\n// ===== WEBGL SETUP =====\n// ===== RENDERING =====\n// ===== GAME LOGIC =====\n// ===== INPUT =====\n// ===== MAIN =====\n```\n\nLet me write it now. For real this time.\n\nI realize I should also handle the \"bag\" randomizer used in modern Tetris (all 7 pieces appear once before repeating, shuffled). This prevents long droughts of certain pieces.\n\n7-bag randomizer:\n1. Create an array [0,1,2,3,4,5,6]\n2. Shuffle it\n3. Deal pieces from this bag\n4. When bag is empty, create a new shuffled bag\n\nThis is the standard modern Tetris randomizer and makes the game much fairer.\n\nOK, let me write the actual code now. I'll create comprehensive, working code.\n\nHere's my complete implementation:\n\n```typescript\n// ===== CONSTANTS =====\nconst COLS = 10;\nconst ROWS = 20;\nconst CELL_SIZE = 32;\nconst BOARD_X = 0;\nconst BOARD_Y = 0;\nconst CANVAS_WIDTH = COLS * CELL_SIZE;\nconst CANVAS_HEIGHT = ROWS * CELL_SIZE;\n\nconst DAS_DELAY = 170; // ms before auto-repeat starts\nconst DAS_RATE = 50; // ms between auto-repeats\nconst SOFT_DROP_RATE = 50; // ms between soft drops\nconst LOCK_DELAY = 500; // ms before piece locks (could implement this)\n\n// ===== PIECE DEFINITIONS =====\nconst SHAPES: number[][][] = [\n // I piece\n [\n [0, 0, 0, 0],\n [1, 1, 1, 1],\n [0, 0, 0, 0],\n [0, 0, 0, 0]\n ],\n // O piece\n [\n [1, 1],\n [1, 1]\n ],\n // T piece\n [\n [0, 1, 0],\n [1, 1, 1],\n [0, 0, 0]\n ],\n // S piece\n [\n [0, 1, 1],\n [1, 1, 0],\n [0, 0, 0]\n ],\n // Z piece\n [\n [1, 1, 0],\n [0, 1, 1],\n [0, 0, 0]\n ],\n // J piece\n [\n [1, 0, 0],\n [1, 1, 1],\n [0, 0, 0]\n ],\n // L piece\n [\n [0, 0, 1],\n [1, 1, 1],\n [0, 0, 0]\n ]\n];\n\n// Colors as [R, G, B] normalized to 0-1\nconst COLORS: [number, number, number][] = [\n [0.0, 0.9, 0.9], // I - cyan\n [0.9, 0.9, 0.0], // O - yellow\n [0.6, 0.0, 0.9], // T - purple\n [0.0, 0.9, 0.0], // S - green\n [0.9, 0.0, 0.0], // Z - red\n [0.0, 0.0, 0.9], // J - blue\n [0.9, 0.6, 0.0], // L - orange\n];\n\n// ===== TYPES =====\ninterface Piece {\n type: number; // 0-6\n shape: number[][]; // current rotation state\n x: number; // board column position\n y: number; // board row position\n}\n\n// ===== GAME STATE =====\nlet board: number[][] = [];\nlet currentPiece: Piece | null = null;\nlet nextPieceType: number = -1;\nlet holdPieceType: number = -1;\nlet canHold: boolean = true;\nlet score: number = 0;\nlet level: number = 1;\nlet lines: number = 0;\nlet combo: number = 0;\nlet gameOver: boolean = false;\nlet paused: boolean = false;\nlet bag: number[] = [];\nlet dropInterval: number = 1000;\nlet dropTimer: number = 0;\nlet lastFrameTime: number = 0;\nlet animFrameId: number = ","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"fa481450-8b03-43bf-bdc6-a7d7f99e6f2b","uuid":"6b32a4cd-beeb-4f49-9481-59334f86bcf4"} diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/claude_output.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/claude_output.json @@ -0,0 +1 @@ +{"type":"result","subtype":"success","is_error":false,"duration_ms":861235,"duration_api_ms":847540,"num_turns":39,"result":"Everything compiles cleanly and the output is correct. Here's a summary of what was built:\n\n---\n\n## Tetris — TypeScript + WebGL\n\n**Project location:** `/tmp/loop-bench-718df8zr/tetris/`\n\n### How to play\n\nOpen `index.html` in a browser (or serve it with any static file server):\n\n```bash\ncd /tmp/loop-bench-718df8zr/tetris && python3 -m http.server 8080\n```\n\nThen visit `http://localhost:8080`.\n\n### Controls\n\n| Key | Action |\n|---|---|\n| ← → | Move left/right |\n| ↑ or X | Rotate clockwise |\n| Z | Rotate counter-clockwise |\n| ↓ | Soft drop (faster fall) |\n| Space | Hard drop (instant) |\n| C / Shift | Hold piece |\n| P / Esc | Pause |\n| R | Restart |\n\n### Architecture\n\n- **`src/tetris.ts`** — Single-file TypeScript source containing all game logic and rendering\n- **`build/tetris.js`** — Compiled JavaScript output\n- **`index.html`** — Entry point that loads the game\n\n### Features implemented\n\n| Feature | Details |\n|---|---|\n| **WebGL rendering** | Custom vertex/fragment shaders drawing colored quads with 3D beveled block styling |\n| **7-bag randomizer** | Each set of 7 tetrominos appears exactly once before reshuffling |\n| **Piece rotation** | Clockwise (↑/X) and counter-clockwise (Z) with wall-kick offsets for 8 positions |\n| **Ghost piece** | Semi-transparent preview showing where the piece will land |\n| **Hold piece** | Store one piece and swap it back later (once per drop) |\n| **Line clearing** | Completed rows are removed, remaining rows collapse down |\n| **Scoring** | NES-style: 40/100/300/1200 × (level+1) for 1/2/3/4 lines; soft drop +1/cell, hard drop +2/cell |\n| **Increasing speed** | Gravity decreases from 800ms down to 30ms across 20 levels; level advances every 10 lines |\n| **DAS/ARR** | Delayed Auto Shift (170ms delay, 50ms repeat) for smooth horizontal movement |\n| **Text overlay** | 2D canvas overlay for score, level, lines, controls help, and game-over/pause screens |","stop_reason":"end_turn","session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","total_cost_usd":0.49940099999999993,"usage":{"input_tokens":23547,"cache_creation_input_tokens":0,"cache_read_input_tokens":536000,"output_tokens":17864,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{"glm-5.1":{"inputTokens":23547,"outputTokens":17864,"cacheReadInputTokens":536000,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.49940099999999993,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"4bc89606-2df8-4fbd-ba2f-3638efe4f7d9"} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/claude_stderr.log b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/claude_stderr.log diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/eval_results.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/eval_results.json @@ -0,0 +1,265 @@ +{ + "structural": { + "pass": false, + "checks": [ + { + "name": "entry_point_exists", + "pass": false, + "detail": "no index.html found in workspace root, dist/, or public/" + }, + { + "name": "package_json_exists", + "pass": true, + "detail": "package.json found" + }, + { + "name": "build_succeeds", + "pass": true, + "detail": "no build script defined (static project)" + }, + { + "name": "typescript_compiles", + "pass": false, + "detail": "TypeScript files found but no tsconfig.json" + } + ], + "score": 0.5 + }, + "quality": { + "lint": { + "pass": true, + "errors": 0, + "warnings": 0 + }, + "typecheck": { + "pass": false, + "error": "no tsconfig.json" + }, + "performance": { + "pass": true, + "bundle_size_bytes": 0, + "size_under_512kb": true + }, + "score": 0.67 + }, + "code_analysis": { + "files": { + "total": 18, + "code": 15, + "docs": 0, + "unnecessary": 0, + "unnecessary_list": [] + }, + "lines_of_code": 3570, + "dependencies": { + "production": 0, + "dev": 4, + "total": 4 + }, + "complexity": "over-engineered", + "console_logs": 0, + "magic_numbers": { + "count": 347, + "excessive": true + }, + "function_length": { + "count": 217, + "average": 7.6, + "max": 44, + "long_functions": 0 + }, + "max_nesting_depth": 10, + "global_declarations": 145, + "naming": { + "dominant_style": "camelCase", + "consistency_pct": 95.4, + "camel_case": 1338, + "snake_case": 65 + }, + "error_handling": { + "try_catch_blocks": 4, + "has_error_handling": true + }, + "comments": { + "comment_lines": 149, + "source_lines": 3116, + "ratio_pct": 4.8 + }, + "separation_of_concerns": { + "verdict": "mixed", + "files_with_rendering": 8, + "files_with_logic": 10, + "files_with_both": 7 + }, + "html_validation": { + "valid": false, + "errors": 2 + }, + "duplication_percentage": 0.0, + "score": 0.75 + }, + "transcript_analysis": { + "total_events": 100, + "tool_calls": { + "total": 38, + "bash": 32, + "write": 0, + "edit": 1, + "read": 5 + }, + "wasted_turns": { + "total": 0, + "docs": 0, + "ascii_art": 0, + "server_starts": 0 + }, + "errors_encountered": 0, + "thinking_blocks": 5, + "text_blocks": 15, + "productivity_ratio": 1.0, + "self_tested": false, + "score": 1.0 + }, + "gameplay_bot": { + "pass": false, + "score": 0.25, + "total": 16, + "passed": 4, + "failed": 12, + "report": { + "implementation": { + "renderer": "unknown", + "grid_detected": false, + "grid_bounds": null, + "controls": { + "left": "ArrowLeft", + "right": "ArrowRight", + "down": "ArrowDown", + "rotate": "ArrowUp", + "drop": "Space" + }, + "start_mechanism": "click_canvas", + "score_element_found": false, + "grid_confidence": 0 + }, + "tests": [ + { + "name": "game_loads", + "pass": true, + "detail": "no console errors" + }, + { + "name": "game_starts", + "pass": true, + "detail": "started via click_canvas" + }, + { + "name": "auto_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify auto-drop" + }, + { + "name": "move_left", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_right", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_down", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "rotate", + "pass": false, + "detail": "grid reader unreliable, cannot verify rotation" + }, + { + "name": "all_pieces_rotate", + "pass": false, + "detail": "could not detect any piece rotations via grid reader" + }, + { + "name": "hard_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify hard drop" + }, + { + "name": "piece_locks", + "pass": true, + "detail": "filled cells persist at bottom (grid-verified, 1 lock event(s))" + }, + { + "name": "new_piece_spawns", + "pass": false, + "detail": "could not detect new piece spawning at top via grid reader" + }, + { + "name": "multiple_pieces", + "pass": false, + "detail": "only 3 piece(s) detected, need at least 3" + }, + { + "name": "line_clear", + "pass": false, + "detail": "could not trigger or detect a line clear via grid reader" + }, + { + "name": "score_changes", + "pass": false, + "detail": "no score element found" + }, + { + "name": "game_over", + "pass": true, + "detail": "game stopped after stacking to top" + }, + { + "name": "playable_30s", + "pass": false, + "detail": "only played for 6s" + } + ], + "summary": { + "total": 16, + "passed": 4, + "failed": 12, + "score": 0.25 + }, + "gameplay": { + "pieces_placed": 16, + "lines_cleared": 0, + "max_score_observed": 0, + "play_duration_seconds": 6, + "errors_during_play": 0 + }, + "session": { + "frames": 23, + "events_count": 2, + "pieces_spawned": 0, + "pieces_locked": 3, + "lines_cleared": 0, + "piece_types_seen": [], + "grid_read_success_rate": 0 + }, + "performance": { + "load_time_ms": 19 + }, + "accessibility": { + "issues": [], + "issue_count": 0, + "pass": true + } + } + }, + "outcome_score": 0.125, + "score": 0.125, + "sonarqube": { + "error": "SonarQube not running at localhost:9000", + "score": 0 + } +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/gameplay-bot-report.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/gameplay-bot-report.json @@ -0,0 +1,129 @@ +{ + "implementation": { + "renderer": "unknown", + "grid_detected": false, + "grid_bounds": null, + "controls": { + "left": "ArrowLeft", + "right": "ArrowRight", + "down": "ArrowDown", + "rotate": "ArrowUp", + "drop": "Space" + }, + "start_mechanism": "click_canvas", + "score_element_found": false, + "grid_confidence": 0 + }, + "tests": [ + { + "name": "game_loads", + "pass": true, + "detail": "no console errors" + }, + { + "name": "game_starts", + "pass": true, + "detail": "started via click_canvas" + }, + { + "name": "auto_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify auto-drop" + }, + { + "name": "move_left", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_right", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "move_down", + "pass": false, + "detail": "grid reader unreliable, cannot verify movement" + }, + { + "name": "rotate", + "pass": false, + "detail": "grid reader unreliable, cannot verify rotation" + }, + { + "name": "all_pieces_rotate", + "pass": false, + "detail": "could not detect any piece rotations via grid reader" + }, + { + "name": "hard_drop", + "pass": false, + "detail": "grid reader unreliable, cannot verify hard drop" + }, + { + "name": "piece_locks", + "pass": true, + "detail": "filled cells persist at bottom (grid-verified, 1 lock event(s))" + }, + { + "name": "new_piece_spawns", + "pass": false, + "detail": "could not detect new piece spawning at top via grid reader" + }, + { + "name": "multiple_pieces", + "pass": false, + "detail": "only 3 piece(s) detected, need at least 3" + }, + { + "name": "line_clear", + "pass": false, + "detail": "could not trigger or detect a line clear via grid reader" + }, + { + "name": "score_changes", + "pass": false, + "detail": "no score element found" + }, + { + "name": "game_over", + "pass": true, + "detail": "game stopped after stacking to top" + }, + { + "name": "playable_30s", + "pass": false, + "detail": "only played for 6s" + } + ], + "summary": { + "total": 16, + "passed": 4, + "failed": 12, + "score": 0.25 + }, + "gameplay": { + "pieces_placed": 16, + "lines_cleared": 0, + "max_score_observed": 0, + "play_duration_seconds": 6, + "errors_during_play": 0 + }, + "session": { + "frames": 23, + "events_count": 2, + "pieces_spawned": 0, + "pieces_locked": 3, + "lines_cleared": 0, + "piece_types_seen": [], + "grid_read_success_rate": 0 + }, + "performance": { + "load_time_ms": 19 + }, + "accessibility": { + "issues": [], + "issue_count": 0, + "pass": true + } +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/meta.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/meta.json @@ -0,0 +1,40 @@ +{ + "model": "glm-5.1", + "effort": "high", + "prompt_style": "simple", + "language": "typescript", + "human_language": "en", + "tool_read": "on", + "tool_write": "on", + "tool_edit": "on", + "tool_glob": "on", + "tool_grep": "on", + "linter": "on", + "playwright": "off", + "context_file": "none", + "web_search": "on", + "max_budget": "low", + "tests_provided": "none", + "strategy": "none", + "design_guidance": "none", + "architecture": "none", + "error_checking": "none", + "context_noise": "clean", + "renderer": "webgl", + "provider": "zai", + "task": "tetris", + "actual_model": "glm-5.1", + "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", + "runs_per_cell": 3, + "timeout_seconds": 1200, + "max_budget_usd": 2.0, + "run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2", + "short_id": "14486646", + "short_cell_id": "cd8ad131", + "run_number": 2, + "claude_version": "2.1.92 (Claude Code)", + "started_at": "2026-04-07T02:05:26.450811+00:00", + "wall_time_seconds": 861, + "exit_code": 0, + "completed_at": "2026-04-07T02:19:50.929133+00:00" +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/transcript.jsonl b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run2/transcript.jsonl @@ -0,0 +1,100 @@ +{"type": "harness", "subtype": "config", "model": "glm-5.1", "effort": "high", "tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebSearch", "WebFetch"], "max_budget_usd": 2.0, "timeout_seconds": 1200, "task": "tetris", "language": "typescript", "prompt_style": "simple"} +{"type": "user", "subtype": "prompt", "message": {"role": "user", "content": "Build a playable Tetris game that runs in a browser. It should have all the standard mechanics: piece rotation, line clearing, scoring, and increasing speed. Use keyboard controls for movement and rotation.\n\n\nUse TypeScript.\n\nRender the game using WebGL."}} +{"type":"system","subtype":"init","cwd":"/tmp/loop-bench-718df8zr","session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","tools":["Bash","Edit","Read"],"mcp_servers":[],"model":"glm-5.1","permissionMode":"dontAsk","slash_commands":["update-config","debug","simplify","batch","loop","schedule","claude-api","compact","context","cost","heapdump","init","review","security-review","insights"],"apiKeySource":"apiKeyHelper","claude_code_version":"2.1.92","output_style":"default","agents":["general-purpose","statusline-setup","Explore","Plan"],"skills":["update-config","debug","simplify","batch","loop","schedule","claude-api"],"plugins":[{"name":"claude-hud","path":"/root/.claude/plugins/marketplaces/claude-hud/","source":"claude-hud@claude-hud"},{"name":"rust-analyzer-lsp","path":"/root/.claude/plugins/marketplaces/claude-plugins-official/plugins/rust-analyzer-lsp","source":"rust-analyzer-lsp@claude-plugins-official"}],"uuid":"42b09770-d9d3-4af0-91f4-93e04e52aadb","fast_mode_state":"off"} +{"type":"assistant","message":{"id":"msg_20260407100530e7f57b4f251d4519","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The user wants me to build a playable Tetris game in TypeScript that renders using WebGL and runs in a browser. Let me plan this out:\n\n1. I need to create an HTML file that loads the TypeScript (compiled to JS)\n2. I need a WebGL renderer for the Tetris grid and pieces\n3. Standard Tetris mechanics:\n - 7 tetromino pieces (I, O, T, S, Z, J, L)\n - Piece rotation (wall kicks would be nice)\n - Line clearing\n - Scoring system\n - Increasing speed (gravity)\n - Keyboard controls (left, right, down, rotate, hard drop)\n\nSince TypeScript needs to be compiled, I'll set up a simple project with a build step. Let me create:\n\n1. `index.html` - the main HTML file\n2. `src/game.ts` - the main game logic and WebGL rendering\n3. `tsconfig.json` - TypeScript config\n4. `package.json` - for dependencies\n\nActually, let me keep it simple. I'll write TypeScript source files and use `tsc` to compile them. Let me structure this well.\n\nLet me think about the WebGL rendering approach:\n- I'll use a simple 2D approach with WebGL\n- Draw colored quads for each cell in the grid\n- Use a simple vertex/fragment shader pair\n\nGame state:\n- 10x20 grid\n- Current piece with position and rotation\n- Next piece preview\n- Score, level, lines cleared\n\nLet me create this step by step.\n\nI'll structure it as:\n- `package.json` with typescript dependency\n- `tsconfig.json`\n- `index.html`\n- `src/tetris.ts` - main game file with all logic and WebGL rendering\n\nLet me write this comprehensively.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"f58baa86-4c02-46f7-9ea8-db34dff327ff"} +{"type":"assistant","message":{"id":"msg_20260407100530e7f57b4f251d4519","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"I'll build a complete Tetris game with TypeScript and WebGL rendering. Let me set up the project structure."}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"ea94a3a7-f00e-46f2-8f03-eedcbcf81583"} +{"type":"assistant","message":{"id":"msg_20260407100530e7f57b4f251d4519","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_86f7009bda8a4bc5a2c95364","name":"Bash","input":{"command":"ls /tmp/loop-bench-718df8zr","description":"List current directory"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"2072ae30-3c16-4f28-a183-4e8d65f198b0"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_86f7009bda8a4bc5a2c95364","type":"tool_result","content":"node_modules\npackage.json\npackage-lock.json\ntests-few\ntests-full\n[rerun: b1]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"eb883653-3ebe-4153-8e88-09e7bfafc488","timestamp":"2026-04-07T02:05:51.246Z","tool_use_result":{"stdout":"node_modules\npackage.json\npackage-lock.json\ntests-few\ntests-full","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407100552e5215c16bac74120","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_c026874b77df494980ae71db","name":"Bash","input":{"command":"mkdir -p /tmp/loop-bench-718df8zr/tetris/src","description":"Create project directories"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"a9c971cb-8765-47d1-9cd4-726bfd26e385"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_c026874b77df494980ae71db","type":"tool_result","content":"[rerun: b2]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"fa8e70e7-aa9f-4662-9acb-8123803ae307","timestamp":"2026-04-07T02:06:02.413Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":true}} +{"type":"assistant","message":{"id":"msg_20260407100603bc087773a5724e81","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7fc51ee10429472eb731762c","name":"Read","input":{"file_path":"/tmp/loop-bench-718df8zr/package.json"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"04d122ae-6226-4e31-afc6-0fa8f65ed925"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7fc51ee10429472eb731762c","type":"tool_result","content":"1\t{\n2\t \"name\": \"loop-bench-718df8zr\",\n3\t \"version\": \"1.0.0\",\n4\t \"description\": \"\",\n5\t \"main\": \"index.js\",\n6\t \"scripts\": {\n7\t \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n8\t },\n9\t \"keywords\": [],\n10\t \"author\": \"\",\n11\t \"license\": \"ISC\",\n12\t \"type\": \"commonjs\",\n13\t \"devDependencies\": {\n14\t \"@eslint/js\": \"^10.0.1\",\n15\t \"@types/node\": \"^25.5.2\",\n16\t \"eslint\": \"^10.2.0\",\n17\t \"typescript\": \"^6.0.2\"\n18\t }\n19\t}\n20\t"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"bd5ee5d4-0df9-42ac-a040-ea7b40c6320a","timestamp":"2026-04-07T02:06:11.589Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-718df8zr/package.json","content":"{\n \"name\": \"loop-bench-718df8zr\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n },\n \"keywords\": [],\n \"author\": \"\",\n \"license\": \"ISC\",\n \"type\": \"commonjs\",\n \"devDependencies\": {\n \"@eslint/js\": \"^10.0.1\",\n \"@types/node\": \"^25.5.2\",\n \"eslint\": \"^10.2.0\",\n \"typescript\": \"^6.0.2\"\n }\n}\n","numLines":20,"startLine":1,"totalLines":20}}} +{"type":"assistant","message":{"id":"msg_20260407100612bb3fda41c07648fb","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"Good, TypeScript is already available. Let me create the files.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"8c10a66b-fd71-47f3-98b3-b95e4693aa22"} +{"type":"assistant","message":{"id":"msg_20260407100612bb3fda41c07648fb","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now let me create all the project files:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"57766ade-0a3c-438d-b769-43e28e3a71b8"} +{"type":"assistant","message":{"id":"msg_20260407100612bb3fda41c07648fb","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_07e77e7c87fa403faa8ecb63","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/tsconfig.json << 'TSEOF'\n{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outFile\": \"../build/tetris.js\",\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"sourceMap\": false,\n \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"]\n },\n \"files\": [\"src/shaders.ts\", src/types.ts\", \"src/board.ts\", \"src/pieces.ts\", \"src/renderer.ts\", \"src/game.ts\"]\n}\nTSEOF","description":"Create tsconfig.json"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"44fd6897-20f7-4f14-b07a-0c67b19f61fb"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_07e77e7c87fa403faa8ecb63","type":"tool_result","content":"[rerun: b3]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"8f0f0d98-c4d8-4592-979a-c45200e9e264","timestamp":"2026-04-07T02:06:24.419Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071006250274d6089f16498a","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7c09f8f13afc4eb3bca75ba6","name":"Bash","input":{"command":"mkdir -p /tmp/loop-bench-718df8zr/tetris/build","description":"Create build output directory"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"51dc0275-d4f3-4860-a959-7dc9fbdde3b9"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7c09f8f13afc4eb3bca75ba6","type":"tool_result","content":"[rerun: b4]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"2e5050df-fc79-4381-8773-e4ee208c5c78","timestamp":"2026-04-07T02:06:30.765Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":true}} +{"type":"assistant","message":{"id":"msg_202604071006318cdb1ba4ec154595","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now let me create all the source files. I'll start with types, then shaders, pieces, board, renderer, and game:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"bc6fd33e-5a54-4def-b4ee-03ace22f489c"} +{"type":"assistant","message":{"id":"msg_202604071006318cdb1ba4ec154595","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_c89c9ae19c5b4f0492562cdb","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/src/types.ts << 'EOF'\n// ── Core Types ──\n\nexport enum Color {\n Empty = 0,\n Cyan, // I\n Yellow, // O\n Purple, // T\n Green, // S\n Red, // Z\n Blue, // J\n Orange, // L\n Ghost, // preview of where piece will land\n}\n\nexport interface Position {\n x: number;\n y: number;\n}\n\nexport type Shape = number[][];\n\nexport interface Piece {\n shape: Shape;\n color: Color;\n pos: Position;\n}\n\nexport type Grid = Color[][];\n\nexport interface GameState {\n board: Grid;\n current: Piece | null;\n next: Piece | null;\n held: Piece | null;\n canHold: boolean;\n score: number;\n level: number;\n lines: number;\n gameOver: boolean;\n paused: boolean;\n}\n\n// RGBA as [r, g, b, a] in 0..1\nexport type RGBA = [number, number, number, number];\nEOF","description":"Create types.ts"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"c9fbcf99-845e-43c4-983d-72e21fbf75ec"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_c89c9ae19c5b4f0492562cdb","type":"tool_result","content":"[rerun: b5]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"8137e8c7-5779-4353-b429-e69eed510cbc","timestamp":"2026-04-07T02:06:47.769Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407100648edb0ec9b3043459c","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_8562406cf1df4e919dbfafe6","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/src/shaders.ts << 'EOF'\n// ── GLSL Shaders ──\n\nexport const VERTEX_SRC = `\nattribute vec2 a_position;\nattribute vec4 a_color;\n\nuniform vec2 u_resolution;\n\nvarying vec4 v_color;\n\nvoid main() {\n // Convert pixel coords to clip space\n vec2 clip = (a_position / u_resolution) * 2.0 - 1.0;\n gl_Position = vec4(clip * vec2(1, -1), 0, 1);\n v_color = a_color;\n}\n`;\n\nexport const FRAGMENT_SRC = `\nprecision mediump float;\nvarying vec4 v_color;\n\nvoid main() {\n gl_FragColor = v_color;\n}\n`;\nEOF","description":"Create shaders.ts"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"8b2c6aa4-c516-42a1-9cdd-bbf5d5e29825"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_8562406cf1df4e919dbfafe6","type":"tool_result","content":"[rerun: b6]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"92d28a33-dca5-495c-8dce-8ab57ed48453","timestamp":"2026-04-07T02:07:02.947Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407100703b354b80c0bf94a7a","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7020f74a80824ded96cb64ce","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/src/pieces.ts << 'EOF'\n// ── Tetromino Definitions & Bag Randomizer ──\n\nimport { Shape, Color, Piece, Position } from \"./types\";\n\n// Standard Tetris shapes (in their spawn rotation, row-major)\n// 0 = empty, non-zero = filled\nconst SHAPES: { shape: Shape; color: Color }[] = [\n { shape: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], color: Color.Cyan }, // I\n { shape: [[1,1],[1,1]], color: Color.Yellow }, // O\n { shape: [[0,1,0],[1,1,1],[0,0,0]], color: Color.Purple }, // T\n { shape: [[0,1,1],[1,1,0],[0,0,0]], color: Color.Green }, // S\n { shape: [[1,1,0],[0,1,1],[0,0,0]], color: Color.Red }, // Z\n { shape: [[1,0,0],[1,1,1],[0,0,0]], color: Color.Blue }, // J\n { shape: [[0,0,1],[1,1,1],[0,0,0]], color: Color.Orange }, // L\n];\n\n// 7-bag randomizer — ensures every 7 pieces appear once before repeating\nlet bag: number[] = [];\n\nfunction refillBag(): void {\n bag = [0, 1, 2, 3, 4, 5, 6];\n // Fisher-Yates shuffle\n for (let i = bag.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [bag[i], bag[j]] = [bag[j], bag[i]];\n }\n}\n\nexport function nextPieceIndex(): number {\n if (bag.length === 0) refillBag();\n return bag.pop()!;\n}\n\nexport function makePiece(idx: number): Piece {\n const def = SHAPES[idx];\n const shape = def.shape.map(row => [...row]);\n const size = shape.length;\n return {\n shape,\n color: def.color,\n pos: {\n x: Math.floor((10 - size) / 2),\n y: 0,\n },\n };\n}\n\n// Clockwise rotation: transpose then reverse rows\nexport function rotateCW(shape: Shape): Shape {\n const n = shape.length;\n const rotated: Shape = [];\n for (let r = 0; r < n; r++) {\n rotated.push([]);\n for (let c = 0; c < n; c++) {\n rotated[r].push(shape[n - 1 - c][r]);\n }\n }\n return rotated;\n}\n\n// Wall kick offsets to try when rotation causes collision\nexport const KICK_OFFSETS: Position[] = [\n { x: 0, y: 0 },\n { x: -1, y: 0 },\n { x: 1, y: 0 },\n { x: 0, y: -1 },\n { x: -1, y: -1 },\n { x: 1, y: -1 },\n { x: -2, y: 0 },\n { x: 2, y: 0 },\n];\nEOF","description":"Create pieces.ts"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"e552e013-5c05-4d59-a037-5efda6c7e3d2"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7020f74a80824ded96cb64ce","type":"tool_result","content":"[rerun: b7]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"12676be6-b7e4-4b44-af38-9231963d5aa1","timestamp":"2026-04-07T02:07:39.157Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407100739b9965be32eb04e58","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_c900a2324cd040dfa42ffe6b","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/src/board.ts << 'EOF'\n// ── Board Logic ──\n\nimport { Grid, Color, Shape, Position } from \"./types\";\n\nexport const COLS = 10;\nexport const ROWS = 20;\n\nexport function createGrid(): Grid {\n const grid: Grid = [];\n for (let r = 0; r < ROWS; r++) {\n grid.push(new Array<Color>(COLS).fill(Color.Empty));\n }\n return grid;\n}\n\nexport function collides(grid: Grid, shape: Shape, pos: Position): boolean {\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (shape[r][c] === 0) continue;\n const nx = pos.x + c;\n const ny = pos.y + r;\n if (nx < 0 || nx >= COLS || ny >= ROWS) return true;\n if (ny >= 0 && grid[ny][nx] !== Color.Empty) return true;\n }\n }\n return false;\n}\n\nexport function lockPiece(grid: Grid, shape: Shape, pos: Position, color: Color): Grid {\n const g = grid.map(row => [...row]);\n for (let r = 0; r < shape.length; r++) {\n for (let c = 0; c < shape[r].length; c++) {\n if (shape[r][c] === 0) continue;\n const ny = pos.y + r;\n const nx = pos.x + c;\n if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) {\n g[ny][nx] = color;\n }\n }\n }\n return g;\n}\n\nexport function clearLines(grid: Grid): { grid: Grid; cleared: number } {\n const g = grid.map(row => [...row]);\n const remaining = g.filter(row => row.some(c => c === Color.Empty));\n const cleared = ROWS - remaining.length;\n while (remaining.length < ROWS) {\n remaining.unshift(new Array<Color>(COLS).fill(Color.Empty));\n }\n return { grid: remaining, cleared };\n}\n\nexport function ghostY(grid: Grid, shape: Shape, pos: Position): number {\n let gy = pos.y;\n while (!collides(grid, shape, { x: pos.x, y: gy + 1 })) {\n gy++;\n }\n return gy;\n}\n\n// Scoring: NES-style\nexport function calcScore(linesCleared: number, level: number): number {\n const base = [0, 40, 100, 300, 1200];\n return (base[linesCleared] || 0) * (level + 1);\n}\n\n// Speed in ms per gravity tick\nexport function gravityMs(level: number): number {\n // Roughly NES curve\n const speeds = [800, 720, 630, 550, 470, 380, 300, 220, 140, 100, 80, 80, 80, 70, 70, 70, 50, 50, 50, 30];\n return speeds[Math.min(level, speeds.length - 1)];\n}\nEOF","description":"Create board.ts"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"dcdd51bb-6b33-4618-8d75-c8d67bee196f"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_c900a2324cd040dfa42ffe6b","type":"tool_result","content":"[rerun: b8]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"966c3683-3034-496f-be7c-2747847f3088","timestamp":"2026-04-07T02:08:12.233Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071008130d194d6f95ca4d36","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_5bcb3b0040b14321a1d43adf","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/src/renderer.ts << 'EOF'\n// ── WebGL Renderer ──\n\nimport { Color, RGBA, Grid } from \"./types\";\nimport { VERTEX_SRC, FRAGMENT_SRC } from \"./shaders\";\nimport { COLS, ROWS } from \"./board\";\n\nconst CELL = 30; // px per cell\nconst BOARD_PAD = 20;\nconst BOARD_W = COLS * CELL;\nconst BOARD_H = ROWS * CELL;\nconst SIDEBAR_W = 200;\n\nexport const CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD;\nexport const CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD;\n\n// Color palette: index = Color enum value\nconst PALETTE: Record<number, RGBA> = {\n [Color.Empty]: [0.08, 0.08, 0.12, 1],\n [Color.Cyan]: [0.0, 0.9, 0.9, 1],\n [Color.Yellow]: [0.95, 0.9, 0.1, 1],\n [Color.Purple]: [0.7, 0.2, 0.9, 1],\n [Color.Green]: [0.1, 0.9, 0.3, 1],\n [Color.Red]: [0.9, 0.2, 0.2, 1],\n [Color.Blue]: [0.2, 0.3, 0.9, 1],\n [Color.Orange]: [0.95, 0.55, 0.1, 1],\n [Color.Ghost]: [0.3, 0.3, 0.4, 0.35],\n};\n\n// Darker shade for the bevel/border effect\nfunction darker(c: RGBA): RGBA {\n return [c[0] * 0.55, c[1] * 0.55, c[2] * 0.55, c[3]];\n}\nfunction lighter(c: RGBA): RGBA {\n return [Math.min(1, c[0] * 1.35), Math.min(1, c[1] * 1.35), Math.min(1, c[2] * 1.35), c[3]];\n}\n\ninterface GLResources {\n gl: WebGLRenderingContext;\n program: WebGLProgram;\n aPosition: number;\n aColor: number;\n uResolution: WebGLUniformLocation | null;\n buffer: WebGLBuffer;\n}\n\nexport function initGL(canvas: HTMLCanvasElement): GLResources {\n const gl = canvas.getContext(\"webgl\", { alpha: false })!;\n gl.viewport(0, 0, canvas.width, canvas.height);\n gl.clearColor(0.06, 0.06, 0.1, 1);\n\n function compile(type: number, src: string): WebGLShader {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {\n throw new Error(gl.getShaderInfoLog(s)!);\n }\n return s;\n }\n\n const vs = compile(gl.VERTEX_SHADER, VERTEX_SRC);\n const fs = compile(gl.FRAGMENT_SHADER, FRAGMENT_SRC);\n const prog = gl.createProgram()!;\n gl.attachShader(prog, vs);\n gl.attachShader(prog, fs);\n gl.linkProgram(prog);\n if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {\n throw new Error(gl.getProgramInfoLog(prog)!);\n }\n\n gl.useProgram(prog);\n\n return {\n gl,\n program: prog,\n aPosition: gl.getAttribLocation(prog, \"a_position\"),\n aColor: gl.getAttribLocation(prog, \"a_color\"),\n uResolution: gl.getUniformLocation(prog, \"u_resolution\"),\n buffer: gl.createBuffer()!,\n };\n}\n\n// Push a colored quad (2 triangles = 6 vertices) into arrays\nfunction pushQuad(\n verts: number[], colors: number[],\n x: number, y: number, w: number, h: number,\n c: RGBA\n) {\n // Triangle 1\n verts.push(x, y, x+w, y, x, y+h);\n // Triangle 2\n verts.push(x+w, y, x+w, y+h, x, y+h);\n for (let i = 0; i < 6; i++) colors.push(...c);\n}\n\n// Push a cell with a bevel/3D block look\nfunction pushBlock(\n verts: number[], colors: number[],\n x: number, y: number, s: number,\n c: RGBA\n) {\n const b = 2; // bevel width\n // Base (darker border)\n pushQuad(verts, colors, x, y, s, s, darker(c));\n // Top face\n pushQuad(verts, colors, x + b, y + b, s - 2*b, s - 2*b, c);\n // Highlight edge (top + left)\n pushQuad(verts, colors, x + b, y + b, s - 2*b, 2, lighter(c));\n pushQuad(verts, colors, x + b, y + b, 2, s - 2*b, lighter(c));\n}\n\nexport function render(\n res: GLResources,\n board: Grid,\n currentShape: number[][] | null,\n currentColor: Color | null,\n currentPos: { x: number; y: number } | null,\n ghostShape: number[][] | null,\n ghostPos: { x: number; y: number } | null,\n nextShape: number[][] | null,\n nextColor: Color | null,\n heldShape: number[][] | null,\n heldColor: Color | null,\n score: number,\n level: number,\n lines: number,\n gameOver: boolean,\n paused: boolean,\n): void {\n const { gl, aPosition, aColor, uResolution, buffer } = res;\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.uniform2f(uResolution, CANVAS_W, CANVAS_H);\n\n const verts: number[] = [];\n const cols: number[] = [];\n\n // ── Board background ──\n pushQuad(verts, cols, BOARD_PAD, BOARD_PAD, BOARD_W, BOARD_H, [0.05, 0.05, 0.08, 1]);\n\n // ── Grid lines ──\n const gridColor: RGBA = [0.12, 0.12, 0.18, 1];\n for (let c = 0; c <= COLS; c++) {\n const x = BOARD_PAD + c * CELL;\n pushQuad(verts, cols, x, BOARD_PAD, 1, BOARD_H, gridColor);\n }\n for (let r = 0; r <= ROWS; r++) {\n const y = BOARD_PAD + r * CELL;\n pushQuad(verts, cols, BOARD_PAD, y, BOARD_W, 1, gridColor);\n }\n\n // ── Locked board cells ──\n for (let r = 0; r < ROWS; r++) {\n for (let c = 0; c < COLS; c++) {\n if (board[r][c] !== Color.Empty) {\n const px = BOARD_PAD + c * CELL;\n const py = BOARD_PAD + r * CELL;\n pushBlock(verts, cols, px, py, CELL, PALETTE[board[r][c]]);\n }\n }\n }\n\n // ── Ghost piece ──\n if (ghostShape && ghostPos) {\n for (let r = 0; r < ghostShape.length; r++) {\n for (let c = 0; c < ghostShape[r].length; c++) {\n if (ghostShape[r][c] === 0) continue;\n const gy = ghostPos.y + r;\n const gx = ghostPos.x + c;\n if (gy < 0) continue;\n const px = BOARD_PAD + gx * CELL;\n const py = BOARD_PAD + gy * CELL;\n pushQuad(verts, cols, px + 1, py + 1, CELL - 2, CELL - 2, PALETTE[Color.Ghost]);\n }\n }\n }\n\n // ── Current piece ──\n if (currentShape && currentColor !== null && currentPos) {\n const c = PALETTE[currentColor];\n for (let r = 0; r < currentShape.length; r++) {\n for (let cc = 0; cc < currentShape[r].length; cc++) {\n if (currentShape[r][cc] === 0) continue;\n const py = currentPos.y + r;\n const px = currentPos.x + cc;\n if (py < 0) continue;\n const bx = BOARD_PAD + px * CELL;\n const by = BOARD_PAD + py * CELL;\n pushBlock(verts, cols, bx, by, CELL, c);\n }\n }\n }\n\n // ── Sidebar ──\n const sx = BOARD_PAD + BOARD_W + BOARD_PAD;\n\n // Helper: draw text info using quads isn't practical, so we'll overlay with a 2D canvas.\n // For now, draw sidebar panels as colored rects — we'll render text separately.\n\n // Next piece panel\n const panelH = 130;\n pushQuad(verts, cols, sx, BOARD_PAD, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]);\n if (nextShape && nextColor !== null) {\n const nc = PALETTE[nextColor];\n const size = nextShape.length;\n const mini = 22;\n const ox = sx + (SIDEBAR_W - 20) / 2 - (size * mini) / 2;\n const oy = BOARD_PAD + 30;\n for (let r = 0; r < size; r++) {\n for (let c = 0; c < size; c++) {\n if (nextShape[r][c] === 0) continue;\n pushBlock(verts, cols, ox + c * mini, oy + r * mini, mini, nc);\n }\n }\n }\n\n // Hold piece panel\n const holdY = BOARD_PAD + panelH + 15;\n pushQuad(verts, cols, sx, holdY, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]);\n if (heldShape && heldColor !== null) {\n const hc = PALETTE[heldColor];\n const size = heldShape.length;\n const mini = 22;\n const ox = sx + (SIDEBAR_W - 20) / 2 - (size * mini) / 2;\n const oy = holdY + 30;\n for (let r = 0; r < size; r++) {\n for (let c = 0; c < size; c++) {\n if (heldShape[r][c] === 0) continue;\n pushBlock(verts, cols, ox + c * mini, oy + r * mini, mini, hc);\n }\n }\n }\n\n // ── Upload & Draw ──\n const vData = new Float32Array(verts.length + cols.length);\n const stride = 6; // 2 pos + 4 color\n for (let i = 0, vi = 0, ci = 0; i < vData.length; i += stride) {\n vData[i] = verts[vi++];\n vData[i+1] = verts[vi++];\n vData[i+2] = cols[ci++];\n vData[i+3] = cols[ci++];\n vData[i+4] = cols[ci++];\n vData[i+5] = cols[ci++];\n }\n\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer);\n gl.bufferData(gl.ARRAY_BUFFER, vData, gl.DYNAMIC_DRAW);\n\n const byteStride = stride * 4;\n gl.enableVertexAttribArray(aPosition);\n gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, byteStride, 0);\n gl.enableVertexAttribArray(aColor);\n gl.vertexAttribPointer(aColor, 4, gl.FLOAT, false, byteStride, 2 * 4);\n\n gl.drawArrays(gl.TRIANGLES, 0, vData.length / stride);\n\n // ── Text overlay via 2D context ──\n drawTextOverlay(\n canvas2d!, sx, BOARD_PAD, holdY, panelH,\n score, level, lines, gameOver, paused\n );\n}\n\n// We maintain a second hidden 2D canvas overlaid for text\nlet canvas2d: HTMLCanvasElement | null = null;\nlet ctx2d: CanvasRenderingContext2D | null = null;\n\nexport function initTextOverlay(parent: HTMLElement): void {\n canvas2d = document.createElement(\"canvas\");\n canvas2d.width = CANVAS_W;\n canvas2d.height = CANVAS_H;\n canvas2d.style.position = \"absolute\";\n canvas2d.style.left = \"0\";\n canvas2d.style.top = \"0\";\n canvas2d.style.pointerEvents = \"none\";\n parent.style.position = \"relative\";\n parent.appendChild(canvas2d);\n ctx2d = canvas2d.getContext(\"2d\")!;\n}\n\nfunction drawTextOverlay(\n cv: HTMLCanvasElement,\n sx: number,\n topPad: number,\n holdY: number,\n panelH: number,\n score: number, level: number, lines: number,\n gameOver: boolean, paused: boolean\n): void {\n if (!ctx2d) return;\n const ctx = ctx2d;\n ctx.clearRect(0, 0, cv.width, cv.height);\n ctx.font = \"bold 14px 'Segoe UI', Arial, sans-serif\";\n ctx.fillStyle = \"#aaa\";\n\n // Next label\n ctx.fillText(\"NEXT\", sx + 8, topPad + 18);\n // Hold label\n ctx.fillText(\"HOLD\", sx + 8, holdY + 18);\n\n // Score / Level / Lines\n const infoY = holdY + panelH + 30;\n ctx.fillStyle = \"#ccc\";\n ctx.font = \"bold 13px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"SCORE\", sx + 8, infoY);\n ctx.fillStyle = \"#fff\";\n ctx.font = \"bold 22px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(score.toLocaleString(), sx + 8, infoY + 24);\n\n ctx.fillStyle = \"#ccc\";\n ctx.font = \"bold 13px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"LEVEL\", sx + 8, infoY + 54);\n ctx.fillStyle = \"#fff\";\n ctx.font = \"bold 22px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(String(level + 1), sx + 8, infoY + 78);\n\n ctx.fillStyle = \"#ccc\";\n ctx.font = \"bold 13px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"LINES\", sx + 8, infoY + 108);\n ctx.fillStyle = \"#fff\";\n ctx.font = \"bold 22px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(String(lines), sx + 8, infoY + 132);\n\n // Controls help\n const helpY = infoY + 172;\n ctx.fillStyle = \"#666\";\n ctx.font = \"12px 'Segoe UI', Arial, sans-serif\";\n const controls = [\n \"← → Move\",\n \"↑ / Z Rotate\",\n \"↓ Soft Drop\",\n \"Space Hard Drop\",\n \"C Hold\",\n \"P Pause\",\n \"R Restart\",\n ];\n controls.forEach((t, i) => ctx.fillText(t, sx + 8, helpY + i * 18));\n\n // Overlays\n if (gameOver) {\n ctx.fillStyle = \"rgba(0,0,0,0.7)\";\n ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 40, BOARD_W, 80);\n ctx.fillStyle = \"#ff4444\";\n ctx.font = \"bold 28px 'Segoe UI', Arial, sans-serif\";\n ctx.textAlign = \"center\";\n ctx.fillText(\"GAME OVER\", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4);\n ctx.fillStyle = \"#ccc\";\n ctx.font = \"16px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"Press R to restart\", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 28);\n ctx.textAlign = \"left\";\n }\n if (paused && !gameOver) {\n ctx.fillStyle = \"rgba(0,0,0,0.6)\";\n ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 30, BOARD_W, 60);\n ctx.fillStyle = \"#ffdd44\";\n ctx.font = \"bold 28px 'Segoe UI', Arial, sans-serif\";\n ctx.textAlign = \"center\";\n ctx.fillText(\"PAUSED\", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4);\n ctx.textAlign = \"left\";\n }\n}\nEOF","description":"Create renderer.ts"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"463bfa46-5e7a-430d-8f20-756d10a37879"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_5bcb3b0040b14321a1d43adf","type":"tool_result","content":"[rerun: b9]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"8d9ba6d3-b174-426d-9052-4779a47f3de4","timestamp":"2026-04-07T02:10:18.970Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_2026040710102078717f47ab7c4c11","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now let me create the main game module that ties everything together:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"5275cc86-bcdf-41dd-861b-cc2f8535c9e9"} +{"type":"assistant","message":{"id":"msg_2026040710102078717f47ab7c4c11","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_d0976871ba7a44e192a9c1d0","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/src/game.ts << 'GEOF'\n// ── Main Game Entry Point ──\n\nimport { Color, Piece, Shape, GameState } from \"./types\";\nimport { nextPieceIndex, makePiece, rotateCW, KICK_OFFSETS } from \"./pieces\";\nimport { createGrid, collides, lockPiece, clearLines, ghostY, calcScore, gravityMs, COLS, ROWS } from \"./board\";\nimport { initGL, initTextOverlay, render, CANVAS_W, CANVAS_H } from \"./renderer\";\n\n// ── Game State ──\nlet state: GameState;\nlet glRes: ReturnType<typeof initGL>;\nlet lastTick: number;\nlet animId: number;\nlet softDropping: boolean;\n\nfunction newState(): GameState {\n const cur = makePiece(nextPieceIndex());\n const nxt = makePiece(nextPieceIndex());\n return {\n board: createGrid(),\n current: cur,\n next: nxt,\n held: null,\n canHold: true,\n score: 0,\n level: 0,\n lines: 0,\n gameOver: false,\n paused: false,\n };\n}\n\n// ── Piece Spawning & Movement ──\n\nfunction spawnNext(): void {\n state.current = state.next;\n state.next = makePiece(nextPieceIndex());\n state.canHold = true;\n if (collides(state.board, state.current.shape, state.current.pos)) {\n state.gameOver = true;\n }\n}\n\nfunction move(dx: number, dy: number): boolean {\n if (!state.current) return false;\n const newPos = { x: state.current.pos.x + dx, y: state.current.pos.y + dy };\n if (!collides(state.board, state.current.shape, newPos)) {\n state.current.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction tryRotate(): void {\n if (!state.current) return;\n const rotated = rotateCW(state.current.shape);\n for (const kick of KICK_OFFSETS) {\n const newPos = { x: state.current.pos.x + kick.x, y: state.current.pos.y + kick.y };\n if (!collides(state.board, rotated, newPos)) {\n state.current.shape = rotated;\n state.current.pos = newPos;\n return;\n }\n }\n}\n\nfunction hardDrop(): void {\n if (!state.current) return;\n const gy = ghostY(state.board, state.current.shape, state.current.pos);\n state.score += (gy - state.current.pos.y) * 2;\n state.current.pos.y = gy;\n lockAndAdvance();\n}\n\nfunction lockAndAdvance(): void {\n if (!state.current) return;\n state.board = lockPiece(state.board, state.current.shape, state.current.pos, state.current.color);\n const { grid: newBoard, cleared } = clearLines(state.board);\n state.board = newBoard;\n if (cleared > 0) {\n state.lines += cleared;\n state.score += calcScore(cleared, state.level);\n state.level = Math.floor(state.lines / 10);\n }\n spawnNext();\n}\n\nfunction holdPiece(): void {\n if (!state.current || !state.canHold) return;\n state.canHold = false;\n const curColor = state.current.color;\n const curShape = state.current.shape.map(r => [...r]);\n if (state.held) {\n // Swap\n const heldColor = state.held.color;\n const heldShape = state.held.shape;\n state.current = makePieceFrom(heldShape, heldColor);\n state.held = { shape: curShape, color: curColor, pos: { x: 0, y: 0 } };\n } else {\n state.held = { shape: curShape, color: curColor, pos: { x: 0, y: 0 } };\n spawnNext();\n }\n}\n\nfunction makePieceFrom(shape: Shape, color: Color): Piece {\n const size = shape.length;\n return {\n shape: shape.map(r => [...r]),\n color,\n pos: {\n x: Math.floor((COLS - size) / 2),\n y: 0,\n },\n };\n}\n\n// ── Gravity Tick ──\n\nfunction tick(now: number): void {\n animId = requestAnimationFrame(tick);\n\n if (state.gameOver || state.paused) {\n draw();\n return;\n }\n\n const elapsed = now - lastTick;\n const interval = softDropping ? Math.min(gravityMs(state.level), 50) : gravityMs(state.level);\n\n if (elapsed >= interval) {\n lastTick = now;\n if (softDropping) state.score += 1;\n if (!move(0, 1)) {\n lockAndAdvance();\n }\n }\n\n draw();\n}\n\nfunction draw(): void {\n const cur = state.current;\n const ghostPos = cur ? { x: cur.pos.x, y: ghostY(state.board, cur.shape, cur.pos) } : null;\n\n render(\n glRes,\n state.board,\n cur ? cur.shape : null,\n cur ? cur.color : null,\n cur ? cur.pos : null,\n cur ? cur.shape : null,\n ghostPos,\n state.next ? state.next.shape : null,\n state.next ? state.next.color : null,\n state.held ? state.held.shape : null,\n state.held ? state.held.color : null,\n state.score,\n state.level,\n state.lines,\n state.gameOver,\n state.paused,\n );\n}\n\n// ── Input ──\n\nlet keyRepeatTimers: Record<string, number> = {};\nconst DAS = 170; // Delayed Auto Shift (ms)\nconst ARR = 50; // Auto Repeat Rate (ms)\n\nfunction handleKey(e: KeyboardEvent, down: boolean): void {\n if (down) {\n if (e.repeat) return; // we handle repeat ourselves for movement\n switch (e.code) {\n case \"ArrowLeft\":\n e.preventDefault();\n move(-1, 0);\n startRepeat(\"ArrowLeft\", () => move(-1, 0));\n break;\n case \"ArrowRight\":\n e.preventDefault();\n move(1, 0);\n startRepeat(\"ArrowRight\", () => move(1, 0));\n break;\n case \"ArrowDown\":\n e.preventDefault();\n softDropping = true;\n break;\n case \"ArrowUp\":\n case \"KeyX\":\n e.preventDefault();\n tryRotate();\n break;\n case \"KeyZ\":\n e.preventDefault();\n // Counter-clockwise = 3 clockwise rotations\n tryRotate(); tryRotate(); tryRotate();\n break;\n case \"Space\":\n e.preventDefault();\n hardDrop();\n break;\n case \"KeyC\":\n case \"ShiftLeft\":\n case \"ShiftRight\":\n e.preventDefault();\n holdPiece();\n break;\n case \"KeyP\":\n case \"Escape\":\n state.paused = !state.paused;\n break;\n case \"KeyR\":\n resetGame();\n break;\n }\n } else {\n switch (e.code) {\n case \"ArrowLeft\":\n case \"ArrowRight\":\n stopRepeat(e.code);\n break;\n case \"ArrowDown\":\n softDropping = false;\n break;\n }\n }\n}\n\nfunction startRepeat(code: string, fn: () => void): void {\n stopRepeat(code);\n const id = window.setTimeout(() => {\n keyRepeatTimers[code] = window.setInterval(fn, ARR);\n }, DAS);\n keyRepeatTimers[code + \"_das\"] = id;\n}\n\nfunction stopRepeat(code: string): void {\n clearTimeout(keyRepeatTimers[code + \"_das\"]);\n clearInterval(keyRepeatTimers[code]);\n delete keyRepeatTimers[code + \"_das\"];\n delete keyRepeatTimers[code];\n}\n\n// ── Init ──\n\nfunction resetGame(): void {\n state = newState();\n lastTick = performance.now();\n softDropping = false;\n // Clear repeat timers\n for (const k of Object.keys(keyRepeatTimers)) {\n clearTimeout(keyRepeatTimers[k]);\n clearInterval(keyRepeatTimers[k]);\n }\n keyRepeatTimers = {};\n}\n\nfunction start(): void {\n const wrapper = document.getElementById(\"game-wrapper\")!;\n const canvas = document.createElement(\"canvas\");\n canvas.id = \"tetris-canvas\";\n canvas.width = CANVAS_W;\n canvas.height = CANVAS_H;\n canvas.style.display = \"block\";\n canvas.style.borderRadius = \"4px\";\n canvas.tabIndex = 0;\n wrapper.appendChild(canvas);\n\n glRes = initGL(canvas);\n initTextOverlay(wrapper);\n\n resetGame();\n\n canvas.addEventListener(\"keydown\", e => handleKey(e, true));\n canvas.addEventListener(\"keyup\", e => handleKey(e, false));\n canvas.focus();\n\n animId = requestAnimationFrame(tick);\n}\n\n// Boot\nif (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", start);\n} else {\n start();\n}\nGEOF","description":"Create game.ts"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"2bedb7ef-d308-459c-9efe-bd00dad9c083"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_d0976871ba7a44e192a9c1d0","type":"tool_result","content":"[rerun: b10]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"5887dac9-dc7f-481c-bba8-1244e630a65e","timestamp":"2026-04-07T02:11:36.045Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101137a4edc841545a47a8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now the HTML file:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"2fd01bbb-387f-4817-9b84-ce0b037be884"} +{"type":"assistant","message":{"id":"msg_20260407101137a4edc841545a47a8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_e40fde3994ef4b16a486bc6b","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/index.html << 'HTML'\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Tetris – WebGL</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n background: #0c0c14;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100vh;\n font-family: 'Segoe UI', Arial, sans-serif;\n overflow: hidden;\n }\n h1 {\n color: #e0e0e0;\n font-size: 24px;\n margin-bottom: 12px;\n letter-spacing: 4px;\n text-transform: uppercase;\n }\n #game-wrapper {\n position: relative;\n display: inline-block;\n }\n canvas { outline: none; }\n #game-wrapper canvas + canvas {\n /* text overlay canvas sits on top */\n }\n </style>\n</head>\n<body>\n <h1>Tetris</h1>\n <div id=\"game-wrapper\"></div>\n <script src=\"build/tetris.js\"></script>\n</body>\n</html>\nHTML","description":"Create index.html"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"63064d27-43eb-43af-aa44-c298f3e527f1"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_e40fde3994ef4b16a486bc6b","type":"tool_result","content":"[rerun: b11]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"d1d6a6ec-0aac-4ac0-a40a-5344d5c2bce4","timestamp":"2026-04-07T02:11:50.898Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101151c093ca2502b74458","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now let me fix the tsconfig (I had a typo with the missing quote) and compile:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"1971275d-e661-441f-b166-94a787038aa5"} +{"type":"assistant","message":{"id":"msg_20260407101151c093ca2502b74458","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_00b29393a9ba4c8594004130","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/tsconfig.json << 'EOF'\n{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outFile\": \"../build/tetris.js\",\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"sourceMap\": false,\n \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"]\n },\n \"files\": [\n \"src/types.ts\",\n \"src/shaders.ts\",\n \"src/pieces.ts\",\n \"src/board.ts\",\n \"src/renderer.ts\",\n \"src/game.ts\"\n ]\n}\nEOF","description":"Fix tsconfig.json with correct file list"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"a0c17df6-13c3-4372-83f3-aa9653f4ca37"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_00b29393a9ba4c8594004130","type":"tool_result","content":"[rerun: b12]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"3c75dff4-65b9-4db2-a128-3f7eb39a5265","timestamp":"2026-04-07T02:12:02.834Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071012045a2b88bdc33249f1","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_abe7418563754b34bd654a56","name":"Bash","input":{"command":"cd /tmp/loop-bench-718df8zr/tetris && npx tsc 2>&1","description":"Compile TypeScript"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"75af0097-e369-4d00-81d7-b201ddc9248c"} +{"type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"Exit code 2\ntsconfig.json(4,15): error TS5107: Option 'module=None' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '\"ignoreDeprecations\": \"6.0\"' to silence this error.\ntsconfig.json(5,5): error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.\n Visit https://aka.ms/ts6 for migration information.\ntsconfig.json(5,5): error TS5101: Option 'outFile' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '\"ignoreDeprecations\": \"6.0\"' to silence this error.\n[rerun: b13]","is_error":true,"tool_use_id":"call_abe7418563754b34bd654a56"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"c5d41bd4-4bd3-4874-a7c3-03ccbd845ad2","timestamp":"2026-04-07T02:12:10.474Z","tool_use_result":"Error: Exit code 2\ntsconfig.json(4,15): error TS5107: Option 'module=None' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '\"ignoreDeprecations\": \"6.0\"' to silence this error.\ntsconfig.json(5,5): error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.\n Visit https://aka.ms/ts6 for migration information.\ntsconfig.json(5,5): error TS5101: Option 'outFile' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '\"ignoreDeprecations\": \"6.0\"' to silence this error.\n[rerun: b13]"} +{"type":"assistant","message":{"id":"msg_202604071012118498b17b2de4402b","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_b8725fa9dbd047adb55205dd","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/tsconfig.json << 'EOF'\n{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"none\",\n \"outFile\": \"../build/tetris.js\",\n \"rootDir\": \"./src\",\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"sourceMap\": false,\n \"ignoreDeprecations\": \"6.0\",\n \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"]\n },\n \"files\": [\n \"src/types.ts\",\n \"src/shaders.ts\",\n \"src/pieces.ts\",\n \"src/board.ts\",\n \"src/renderer.ts\",\n \"src/game.ts\"\n ]\n}\nEOF","description":"Update tsconfig to fix deprecation warnings"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"3f2d582a-cea5-4773-aeaa-b20b08ef647d"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_b8725fa9dbd047adb55205dd","type":"tool_result","content":"[rerun: b14]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"97f6d918-7f47-4b6d-b270-48849f2aeb18","timestamp":"2026-04-07T02:12:21.952Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101223fcc05406382b4657","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_8f241f72c3d6497cbd493578","name":"Bash","input":{"command":"cd /tmp/loop-bench-718df8zr/tetris && npx tsc 2>&1","description":"Compile TypeScript (attempt 2)"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"1b31d043-bdb3-4cea-96ce-f1bbf905fb8c"} +{"type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"Exit code 2\nsrc/game.ts(38,29): error TS18047: 'state.current' is possibly 'null'.\nsrc/game.ts(38,50): error TS18047: 'state.current' is possibly 'null'.\n[rerun: b15]","is_error":true,"tool_use_id":"call_8f241f72c3d6497cbd493578"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"17157460-f7ec-4a48-80c8-863cbae6b007","timestamp":"2026-04-07T02:12:31.794Z","tool_use_result":"Error: Exit code 2\nsrc/game.ts(38,29): error TS18047: 'state.current' is possibly 'null'.\nsrc/game.ts(38,50): error TS18047: 'state.current' is possibly 'null'.\n[rerun: b15]"} +{"type":"assistant","message":{"id":"msg_20260407101233b65732efe6124155","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_2610e1bed368495abacf9199","name":"Read","input":{"file_path":"/tmp/loop-bench-718df8zr/tetris/src/game.ts","offset":35,"limit":10}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"fa06c996-db0b-4a5b-83ed-d13c5db2ee27"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_2610e1bed368495abacf9199","type":"tool_result","content":"35\t state.current = state.next;\n36\t state.next = makePiece(nextPieceIndex());\n37\t state.canHold = true;\n38\t if (collides(state.board, state.current.shape, state.current.pos)) {\n39\t state.gameOver = true;\n40\t }\n41\t}\n42\t\n43\tfunction move(dx: number, dy: number): boolean {\n44\t if (!state.current) return false;"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"411bf592-53c7-4dd7-808c-a93eb2be3add","timestamp":"2026-04-07T02:12:39.874Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-718df8zr/tetris/src/game.ts","content":" state.current = state.next;\n state.next = makePiece(nextPieceIndex());\n state.canHold = true;\n if (collides(state.board, state.current.shape, state.current.pos)) {\n state.gameOver = true;\n }\n}\n\nfunction move(dx: number, dy: number): boolean {\n if (!state.current) return false;","numLines":10,"startLine":35,"totalLines":287}}} +{"type":"assistant","message":{"id":"msg_20260407101241e080296db69e482e","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7daa2348eb4641f9a53bd573","name":"Edit","input":{"replace_all":false,"file_path":"/tmp/loop-bench-718df8zr/tetris/src/game.ts","old_string":" if (collides(state.board, state.current.shape, state.current.pos)) {\n state.gameOver = true;\n }","new_string":" if (state.current && collides(state.board, state.current.shape, state.current.pos)) {\n state.gameOver = true;\n }"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"f88e52d5-bc1c-473c-8520-b595042b8a32"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7daa2348eb4641f9a53bd573","type":"tool_result","content":"The file /tmp/loop-bench-718df8zr/tetris/src/game.ts has been updated successfully."}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"15a4cc28-0824-42e1-9e36-c03c295efab4","timestamp":"2026-04-07T02:12:49.130Z","tool_use_result":{"filePath":"/tmp/loop-bench-718df8zr/tetris/src/game.ts","oldString":" if (collides(state.board, state.current.shape, state.current.pos)) {\n state.gameOver = true;\n }","newString":" if (state.current && collides(state.board, state.current.shape, state.current.pos)) {\n state.gameOver = true;\n }","originalFile":"// ── Main Game Entry Point ──\n\nimport { Color, Piece, Shape, GameState } from \"./types\";\nimport { nextPieceIndex, makePiece, rotateCW, KICK_OFFSETS } from \"./pieces\";\nimport { createGrid, collides, lockPiece, clearLines, ghostY, calcScore, gravityMs, COLS, ROWS } from \"./board\";\nimport { initGL, initTextOverlay, render, CANVAS_W, CANVAS_H } from \"./renderer\";\n\n// ── Game State ──\nlet state: GameState;\nlet glRes: ReturnType<typeof initGL>;\nlet lastTick: number;\nlet animId: number;\nlet softDropping: boolean;\n\nfunction newState(): GameState {\n const cur = makePiece(nextPieceIndex());\n const nxt = makePiece(nextPieceIndex());\n return {\n board: createGrid(),\n current: cur,\n next: nxt,\n held: null,\n canHold: true,\n score: 0,\n level: 0,\n lines: 0,\n gameOver: false,\n paused: false,\n };\n}\n\n// ── Piece Spawning & Movement ──\n\nfunction spawnNext(): void {\n state.current = state.next;\n state.next = makePiece(nextPieceIndex());\n state.canHold = true;\n if (collides(state.board, state.current.shape, state.current.pos)) {\n state.gameOver = true;\n }\n}\n\nfunction move(dx: number, dy: number): boolean {\n if (!state.current) return false;\n const newPos = { x: state.current.pos.x + dx, y: state.current.pos.y + dy };\n if (!collides(state.board, state.current.shape, newPos)) {\n state.current.pos = newPos;\n return true;\n }\n return false;\n}\n\nfunction tryRotate(): void {\n if (!state.current) return;\n const rotated = rotateCW(state.current.shape);\n for (const kick of KICK_OFFSETS) {\n const newPos = { x: state.current.pos.x + kick.x, y: state.current.pos.y + kick.y };\n if (!collides(state.board, rotated, newPos)) {\n state.current.shape = rotated;\n state.current.pos = newPos;\n return;\n }\n }\n}\n\nfunction hardDrop(): void {\n if (!state.current) return;\n const gy = ghostY(state.board, state.current.shape, state.current.pos);\n state.score += (gy - state.current.pos.y) * 2;\n state.current.pos.y = gy;\n lockAndAdvance();\n}\n\nfunction lockAndAdvance(): void {\n if (!state.current) return;\n state.board = lockPiece(state.board, state.current.shape, state.current.pos, state.current.color);\n const { grid: newBoard, cleared } = clearLines(state.board);\n state.board = newBoard;\n if (cleared > 0) {\n state.lines += cleared;\n state.score += calcScore(cleared, state.level);\n state.level = Math.floor(state.lines / 10);\n }\n spawnNext();\n}\n\nfunction holdPiece(): void {\n if (!state.current || !state.canHold) return;\n state.canHold = false;\n const curColor = state.current.color;\n const curShape = state.current.shape.map(r => [...r]);\n if (state.held) {\n // Swap\n const heldColor = state.held.color;\n const heldShape = state.held.shape;\n state.current = makePieceFrom(heldShape, heldColor);\n state.held = { shape: curShape, color: curColor, pos: { x: 0, y: 0 } };\n } else {\n state.held = { shape: curShape, color: curColor, pos: { x: 0, y: 0 } };\n spawnNext();\n }\n}\n\nfunction makePieceFrom(shape: Shape, color: Color): Piece {\n const size = shape.length;\n return {\n shape: shape.map(r => [...r]),\n color,\n pos: {\n x: Math.floor((COLS - size) / 2),\n y: 0,\n },\n };\n}\n\n// ── Gravity Tick ──\n\nfunction tick(now: number): void {\n animId = requestAnimationFrame(tick);\n\n if (state.gameOver || state.paused) {\n draw();\n return;\n }\n\n const elapsed = now - lastTick;\n const interval = softDropping ? Math.min(gravityMs(state.level), 50) : gravityMs(state.level);\n\n if (elapsed >= interval) {\n lastTick = now;\n if (softDropping) state.score += 1;\n if (!move(0, 1)) {\n lockAndAdvance();\n }\n }\n\n draw();\n}\n\nfunction draw(): void {\n const cur = state.current;\n const ghostPos = cur ? { x: cur.pos.x, y: ghostY(state.board, cur.shape, cur.pos) } : null;\n\n render(\n glRes,\n state.board,\n cur ? cur.shape : null,\n cur ? cur.color : null,\n cur ? cur.pos : null,\n cur ? cur.shape : null,\n ghostPos,\n state.next ? state.next.shape : null,\n state.next ? state.next.color : null,\n state.held ? state.held.shape : null,\n state.held ? state.held.color : null,\n state.score,\n state.level,\n state.lines,\n state.gameOver,\n state.paused,\n );\n}\n\n// ── Input ──\n\nlet keyRepeatTimers: Record<string, number> = {};\nconst DAS = 170; // Delayed Auto Shift (ms)\nconst ARR = 50; // Auto Repeat Rate (ms)\n\nfunction handleKey(e: KeyboardEvent, down: boolean): void {\n if (down) {\n if (e.repeat) return; // we handle repeat ourselves for movement\n switch (e.code) {\n case \"ArrowLeft\":\n e.preventDefault();\n move(-1, 0);\n startRepeat(\"ArrowLeft\", () => move(-1, 0));\n break;\n case \"ArrowRight\":\n e.preventDefault();\n move(1, 0);\n startRepeat(\"ArrowRight\", () => move(1, 0));\n break;\n case \"ArrowDown\":\n e.preventDefault();\n softDropping = true;\n break;\n case \"ArrowUp\":\n case \"KeyX\":\n e.preventDefault();\n tryRotate();\n break;\n case \"KeyZ\":\n e.preventDefault();\n // Counter-clockwise = 3 clockwise rotations\n tryRotate(); tryRotate(); tryRotate();\n break;\n case \"Space\":\n e.preventDefault();\n hardDrop();\n break;\n case \"KeyC\":\n case \"ShiftLeft\":\n case \"ShiftRight\":\n e.preventDefault();\n holdPiece();\n break;\n case \"KeyP\":\n case \"Escape\":\n state.paused = !state.paused;\n break;\n case \"KeyR\":\n resetGame();\n break;\n }\n } else {\n switch (e.code) {\n case \"ArrowLeft\":\n case \"ArrowRight\":\n stopRepeat(e.code);\n break;\n case \"ArrowDown\":\n softDropping = false;\n break;\n }\n }\n}\n\nfunction startRepeat(code: string, fn: () => void): void {\n stopRepeat(code);\n const id = window.setTimeout(() => {\n keyRepeatTimers[code] = window.setInterval(fn, ARR);\n }, DAS);\n keyRepeatTimers[code + \"_das\"] = id;\n}\n\nfunction stopRepeat(code: string): void {\n clearTimeout(keyRepeatTimers[code + \"_das\"]);\n clearInterval(keyRepeatTimers[code]);\n delete keyRepeatTimers[code + \"_das\"];\n delete keyRepeatTimers[code];\n}\n\n// ── Init ──\n\nfunction resetGame(): void {\n state = newState();\n lastTick = performance.now();\n softDropping = false;\n // Clear repeat timers\n for (const k of Object.keys(keyRepeatTimers)) {\n clearTimeout(keyRepeatTimers[k]);\n clearInterval(keyRepeatTimers[k]);\n }\n keyRepeatTimers = {};\n}\n\nfunction start(): void {\n const wrapper = document.getElementById(\"game-wrapper\")!;\n const canvas = document.createElement(\"canvas\");\n canvas.id = \"tetris-canvas\";\n canvas.width = CANVAS_W;\n canvas.height = CANVAS_H;\n canvas.style.display = \"block\";\n canvas.style.borderRadius = \"4px\";\n canvas.tabIndex = 0;\n wrapper.appendChild(canvas);\n\n glRes = initGL(canvas);\n initTextOverlay(wrapper);\n\n resetGame();\n\n canvas.addEventListener(\"keydown\", e => handleKey(e, true));\n canvas.addEventListener(\"keyup\", e => handleKey(e, false));\n canvas.focus();\n\n animId = requestAnimationFrame(tick);\n}\n\n// Boot\nif (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", start);\n} else {\n start();\n}\n","structuredPatch":[{"oldStart":35,"oldLines":7,"newStart":35,"newLines":7,"lines":[" state.current = state.next;"," state.next = makePiece(nextPieceIndex());"," state.canHold = true;","- if (collides(state.board, state.current.shape, state.current.pos)) {","+ if (state.current && collides(state.board, state.current.shape, state.current.pos)) {"," state.gameOver = true;"," }"," }"]}],"userModified":false,"replaceAll":false}} +{"type":"assistant","message":{"id":"msg_20260407101250f7b6225941504dc9","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_e613b97374c847c89e6cbd5f","name":"Bash","input":{"command":"cd /tmp/loop-bench-718df8zr/tetris && npx tsc 2>&1","description":"Compile TypeScript (attempt 3)"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"f4a4fdc1-4137-4614-9c9b-2155fe71a95e"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_e613b97374c847c89e6cbd5f","type":"tool_result","content":"[rerun: b16]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"72517c17-62ce-46a6-a464-cd4da7db4597","timestamp":"2026-04-07T02:12:56.993Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101258d89e98d1fe8540eb","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Compilation succeeded. Let me verify the output exists and check the size:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"b1b621ec-09e6-431a-a00c-a1bcc8f5558d"} +{"type":"assistant","message":{"id":"msg_20260407101258d89e98d1fe8540eb","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_51e28965eeae4f48aeef26ed","name":"Bash","input":{"command":"wc -l /tmp/loop-bench-718df8zr/build/tetris.js && echo \"---\" && head -5 /tmp/loop-bench-718df8zr/build/tetris.js","description":"Check compiled output"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"e0fae151-3d9d-4bd2-a1a7-9500dce1a9bb"} +{"type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"Exit code 1\nwc: /tmp/loop-bench-718df8zr/build/tetris.js: No such file or directory\n[rerun: b17]","is_error":true,"tool_use_id":"call_51e28965eeae4f48aeef26ed"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"034a6ca6-b60e-439e-8a12-4efef99c5efb","timestamp":"2026-04-07T02:13:05.369Z","tool_use_result":"Error: Exit code 1\nwc: /tmp/loop-bench-718df8zr/build/tetris.js: No such file or directory\n[rerun: b17]"} +{"type":"assistant","message":{"id":"msg_20260407101306fc9fa03fc19f4cb9","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_aa1c76fafde948febf2f387f","name":"Bash","input":{"command":"find /tmp/loop-bench-718df8zr -name \"tetris.js\" 2>/dev/null","description":"Find the compiled JS file"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"97df3a4c-ff9d-486d-a429-67ad4e68d81d"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_aa1c76fafde948febf2f387f","type":"tool_result","content":"[rerun: b18]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"a8548924-4662-4b5b-b913-878b7f08b5bb","timestamp":"2026-04-07T02:13:10.245Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101311c66325a9092f4d96","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_53a820740d5d4a71971277b2","name":"Bash","input":{"command":"ls -la /tmp/loop-bench-718df8zr/tetris/build/ 2>/dev/null; echo \"---\"; ls -la /tmp/loop-bench-718df8zr/build/ 2>/dev/null","description":"Check both possible build directories"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"2d6074b1-fe8e-4b06-87b0-d31a90e3c2d2"} +{"type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"Exit code 2\ntotal 8\ndrwxr-xr-x 2 root root 4096 Apr 7 04:06 .\ndrwxr-xr-x 4 root root 4096 Apr 7 04:11 ..\n---\n[rerun: b19]","is_error":true,"tool_use_id":"call_53a820740d5d4a71971277b2"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"1701e8f2-b03e-41ad-bb2a-d72007872033","timestamp":"2026-04-07T02:13:17.501Z","tool_use_result":"Error: Exit code 2\ntotal 8\ndrwxr-xr-x 2 root root 4096 Apr 7 04:06 .\ndrwxr-xr-x 4 root root 4096 Apr 7 04:11 ..\n---\n[rerun: b19]"} +{"type":"assistant","message":{"id":"msg_2026040710131822b4d813a46640c5","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The `outFile` is set to `../build/tetris.js` relative to the tsconfig.json, which is at `/tmp/loop-bench-718df8zr/tetris/`. So the output should be at `/tmp/loop-bench-718df8zr/build/tetris.js`. But it seems the build directory is empty. Let me check the compilation again more carefully.\n\nActually wait, the build dir is at `/tmp/loop-bench-718df8zr/tetris/build/` and it exists but is empty. The `outFile` path `../build/tetris.js` from tsconfig at `/tmp/loop-bench-718df8zr/tetris/` should go to `/tmp/loop-bench-718df8zr/build/tetris.js`. But the build directory at `/tmp/loop-bench-718df8zr/build/` doesn't exist (there are already files in `/tmp/loop-bench-718df8zr/` including package.json and node_modules).\n\nWait, the compilation appeared to succeed (no errors). Let me run it again and look more carefully.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"c907b290-8fbc-497b-8240-3450abc149b4"} +{"type":"assistant","message":{"id":"msg_2026040710131822b4d813a46640c5","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_bb30d9f8e447442ba6bed7a5","name":"Bash","input":{"command":"npx tsc --listEmittedFiles 2>&1","description":"Compile with listEmittedFiles to see output"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"e594b9a4-ecce-47f9-8e4b-9f3c4db37fad"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_bb30d9f8e447442ba6bed7a5","type":"tool_result","content":"[rerun: b20]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"f4f94b61-bc4f-42b1-a44d-d29aa79c2e8b","timestamp":"2026-04-07T02:13:35.762Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071013369cfae95e88c540ff","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_e99f06a27892426c8ade07a0","name":"Bash","input":{"command":"npx tsc --listEmittedFiles 2>&1","description":"Compile from tetris directory"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"e09d2dff-cc69-4db3-a863-c0e9cc8d731d"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_e99f06a27892426c8ade07a0","type":"tool_result","content":"[rerun: b21]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"161bebc9-8b6e-4c8f-bdea-463de9a65d44","timestamp":"2026-04-07T02:13:43.014Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101344969c43bd2386487c","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_ac5e275c027e40f6b178e2d2","name":"Bash","input":{"command":"npx tsc --listEmittedFiles 2>&1","description":"Compile from tetris directory with correct cwd"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"260b11d1-c10e-4d92-9eb0-ae9a7e22e3a1"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_ac5e275c027e40f6b178e2d2","type":"tool_result","content":"[rerun: b22]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"fa329964-4df5-4d3e-8009-6ac7093c1f3b","timestamp":"2026-04-07T02:13:53.292Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101354fcbe7d7404984bb8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_50f007ea695c4689b8b62a2a","name":"Bash","input":{"command":"npx tsc 2>&1 && echo \"SUCCESS\" && find / -name \"tetris.js\" -newer /tmp/loop-bench-718df8zr/tetris/tsconfig.json 2>/dev/null","description":"Compile and search for output file"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"e8067f87-3608-4d28-8ecb-308dc1f58fdd"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_50f007ea695c4689b8b62a2a","type":"tool_result","content":"SUCCESS\n[rerun: b23]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"6016db7b-0b43-4906-8a6a-94a2bd8a3045","timestamp":"2026-04-07T02:14:11.493Z","tool_use_result":{"stdout":"SUCCESS","stderr":"","interrupted":false,"isImage":false,"returnCodeInterpretation":"Some directories were inaccessible","noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_20260407101412b1265b3d63ac4275","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The compilation succeeds but no file is emitted. With `module: \"none\"` and `outFile`, it should concatenate and emit. But maybe `tsc` version 6 doesn't actually emit with `outFile` anymore since it's deprecated? Let me check.\n\nActually, the issue might be that `outFile` requires a non-modular output but the source files have `import`/`export` statements which make them modules. With `module: \"none\"`, you can't use `import`/`export`. That's the issue.\n\nLet me restructure to not use modules - use namespaces instead, or just concatenate the files differently. Or better, let me just use a bundler approach: compile each file as a module and use a simple concatenation/bundling.\n\nActually the simplest approach: just write everything in a single file. Let me consolidate into one TypeScript file. That will be the easiest approach.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"5aaec414-d400-4bb8-883f-701f6480a973"} +{"type":"assistant","message":{"id":"msg_20260407101412b1265b3d63ac4275","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"The `module: \"none\"` + `outFile` approach doesn't work with ES module imports. Let me consolidate into a single file approach which is simpler and cleaner:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"0c6e3b33-5004-43c7-a1ab-1e907ad77411"} +{"type":"assistant","message":{"id":"msg_20260407101412b1265b3d63ac4275","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_a7a77707320c49a7897a5826","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/src/tetris.ts << 'GEOF'\n// ══════════════════════════════════════════════════════════\n// TETRIS — TypeScript + WebGL\n// Single-file implementation\n// ══════════════════════════════════════════════════════════\n\n// ── Types ──\n\nenum Color {\n Empty = 0,\n Cyan, // I\n Yellow, // O\n Purple, // T\n Green, // S\n Red, // Z\n Blue, // J\n Orange, // L\n}\n\ntype RGBA = [number, number, number, number];\ntype Shape = number[][];\n\ninterface Position { x: number; y: number }\n\ninterface Piece {\n shape: Shape;\n color: Color;\n pos: Position;\n}\n\n// ── Constants ──\n\nconst COLS = 10;\nconst ROWS = 20;\nconst CELL = 30;\nconst BOARD_PAD = 20;\nconst BOARD_W = COLS * CELL;\nconst BOARD_H = ROWS * CELL;\nconst SIDEBAR_W = 200;\nconst CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD;\nconst CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD;\n\nconst PALETTE: Record<number, RGBA> = {\n [Color.Empty]: [0.08, 0.08, 0.12, 1],\n [Color.Cyan]: [0.0, 0.9, 0.9, 1],\n [Color.Yellow]: [0.95, 0.9, 0.1, 1],\n [Color.Purple]: [0.7, 0.2, 0.9, 1],\n [Color.Green]: [0.1, 0.9, 0.3, 1],\n [Color.Red]: [0.9, 0.2, 0.2, 1],\n [Color.Blue]: [0.2, 0.3, 0.9, 1],\n [Color.Orange]: [0.95, 0.55, 0.1, 1],\n};\n\nconst SHAPES: { shape: Shape; color: Color }[] = [\n { shape: [[0,0,0,0],[1,1,1,1],[0,0,0,0],[0,0,0,0]], color: Color.Cyan },\n { shape: [[1,1],[1,1]], color: Color.Yellow },\n { shape: [[0,1,0],[1,1,1],[0,0,0]], color: Color.Purple },\n { shape: [[0,1,1],[1,1,0],[0,0,0]], color: Color.Green },\n { shape: [[1,1,0],[0,1,1],[0,0,0]], color: Color.Red },\n { shape: [[1,0,0],[1,1,1],[0,0,0]], color: Color.Blue },\n { shape: [[0,0,1],[1,1,1],[0,0,0]], color: Color.Orange },\n];\n\nconst KICK_OFFSETS: Position[] = [\n { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 1, y: 0 },\n { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 },\n { x: -2, y: 0 }, { x: 2, y: 0 },\n];\n\n// ── Piece Logic ──\n\nlet bag: number[] = [];\n\nfunction refillBag(): void {\n bag = [0, 1, 2, 3, 4, 5, 6];\n for (let i = bag.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));\n [bag[i], bag[j]] = [bag[j], bag[i]];\n }\n}\n\nfunction nextIdx(): number {\n if (bag.length === 0) refillBag();\n return bag.pop()!;\n}\n\nfunction makePiece(idx: number): Piece {\n const def = SHAPES[idx];\n const shape = def.shape.map(r => [...r]);\n return { shape, color: def.color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } };\n}\n\nfunction makePieceFrom(shape: Shape, color: Color): Piece {\n return { shape: shape.map(r => [...r]), color, pos: { x: Math.floor((COLS - shape.length) / 2), y: 0 } };\n}\n\nfunction rotateCW(shape: Shape): Shape {\n const n = shape.length;\n const out: Shape = [];\n for (let r = 0; r < n; r++) {\n out.push([]);\n for (let c = 0; c < n; c++) out[r].push(shape[n - 1 - c][r]);\n }\n return out;\n}\n\n// ── Board Logic ──\n\ntype Grid = Color[][];\n\nfunction createGrid(): Grid {\n const g: Grid = [];\n for (let r = 0; r < ROWS; r++) g.push(new Array<Color>(COLS).fill(Color.Empty));\n return g;\n}\n\nfunction collides(grid: Grid, shape: Shape, pos: Position): boolean {\n for (let r = 0; r < shape.length; r++)\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const nx = pos.x + c, ny = pos.y + r;\n if (nx < 0 || nx >= COLS || ny >= ROWS) return true;\n if (ny >= 0 && grid[ny][nx] !== Color.Empty) return true;\n }\n return false;\n}\n\nfunction lock(grid: Grid, shape: Shape, pos: Position, color: Color): Grid {\n const g = grid.map(r => [...r]);\n for (let r = 0; r < shape.length; r++)\n for (let c = 0; c < shape[r].length; c++) {\n if (!shape[r][c]) continue;\n const ny = pos.y + r, nx = pos.x + c;\n if (ny >= 0 && ny < ROWS && nx >= 0 && nx < COLS) g[ny][nx] = color;\n }\n return g;\n}\n\nfunction clearLines(grid: Grid): { grid: Grid; cleared: number } {\n const remaining = grid.filter(row => row.some(c => c === Color.Empty));\n const cleared = ROWS - remaining.length;\n while (remaining.length < ROWS) remaining.unshift(new Array<Color>(COLS).fill(Color.Empty));\n return { grid: remaining, cleared };\n}\n\nfunction ghostY(grid: Grid, shape: Shape, pos: Position): number {\n let gy = pos.y;\n while (!collides(grid, shape, { x: pos.x, y: gy + 1 })) gy++;\n return gy;\n}\n\nfunction calcScore(n: number, lvl: number): number {\n return [0, 40, 100, 300, 1200][n] * (lvl + 1);\n}\n\nfunction gravityMs(lvl: number): number {\n const s = [800,720,630,550,470,380,300,220,140,100,80,80,80,70,70,70,50,50,50,30];\n return s[Math.min(lvl, s.length - 1)];\n}\n\n// ── Shaders ──\n\nconst VERT = `\nattribute vec2 a_pos;\nattribute vec4 a_col;\nuniform vec2 u_res;\nvarying vec4 v_col;\nvoid main() {\n vec2 clip = (a_pos / u_res) * 2.0 - 1.0;\n gl_Position = vec4(clip * vec2(1, -1), 0, 1);\n v_col = a_col;\n}`;\n\nconst FRAG = `\nprecision mediump float;\nvarying vec4 v_col;\nvoid main() { gl_FragColor = v_col; }`;\n\n// ── WebGL Renderer ──\n\nlet gl: WebGLRenderingContext;\nlet prog: WebGLProgram;\nlet aPos: number, aCol: number, uRes: WebGLUniformLocation | null;\nlet buf: WebGLBuffer;\n\nfunction initGL(canvas: HTMLCanvasElement): void {\n gl = canvas.getContext(\"webgl\", { alpha: false })!;\n gl.viewport(0, 0, canvas.width, canvas.height);\n gl.clearColor(0.06, 0.06, 0.1, 1);\n\n function comp(type: number, src: string): WebGLShader {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n }\n const vs = comp(gl.VERTEX_SHADER, VERT);\n const fs = comp(gl.FRAGMENT_SHADER, FRAG);\n prog = gl.createProgram()!;\n gl.attachShader(prog, vs);\n gl.attachShader(prog, fs);\n gl.linkProgram(prog);\n gl.useProgram(prog);\n\n aPos = gl.getAttribLocation(prog, \"a_pos\");\n aCol = gl.getAttribLocation(prog, \"a_col\");\n uRes = gl.getUniformLocation(prog, \"u_res\");\n buf = gl.createBuffer()!;\n}\n\nfunction darker(c: RGBA): RGBA { return [c[0]*0.55, c[1]*0.55, c[2]*0.55, c[3]]; }\nfunction lighter(c: RGBA): RGBA { return [Math.min(1,c[0]*1.35), Math.min(1,c[1]*1.35), Math.min(1,c[2]*1.35), c[3]]; }\n\nfunction pushQuad(v: number[], cl: number[], x: number, y: number, w: number, h: number, c: RGBA): void {\n v.push(x,y, x+w,y, x,y+h, x+w,y, x+w,y+h, x,y+h);\n for (let i = 0; i < 6; i++) cl.push(...c);\n}\n\nfunction pushBlock(v: number[], cl: number[], x: number, y: number, s: number, c: RGBA): void {\n const b = 2;\n pushQuad(v, cl, x, y, s, s, darker(c));\n pushQuad(v, cl, x+b, y+b, s-2*b, s-2*b, c);\n pushQuad(v, cl, x+b, y+b, s-2*b, b, lighter(c));\n pushQuad(v, cl, x+b, y+b, b, s-2*b, lighter(c));\n}\n\n// ── State ──\n\nlet board: Grid;\nlet cur: Piece | null;\nlet nxt: Piece | null;\nlet held: Piece | null;\nlet canHold: boolean;\nlet score: number, level: number, linesCleared: number;\nlet gameOver: boolean, paused: boolean;\nlet lastTick: number;\nlet softDrop: boolean;\n\nfunction resetGame(): void {\n board = createGrid();\n cur = makePiece(nextIdx());\n nxt = makePiece(nextIdx());\n held = null;\n canHold = true;\n score = 0; level = 0; linesCleared = 0;\n gameOver = false; paused = false;\n lastTick = performance.now();\n softDrop = false;\n for (const k of Object.keys(keyTimers)) { clearTimeout(keyTimers[k]); clearInterval(keyTimers[k]); }\n keyTimers = {};\n}\n\n// ── Game Actions ──\n\nfunction move(dx: number, dy: number): boolean {\n if (!cur) return false;\n const p = { x: cur.pos.x + dx, y: cur.pos.y + dy };\n if (!collides(board, cur.shape, p)) { cur.pos = p; return true; }\n return false;\n}\n\nfunction tryRotate(): void {\n if (!cur) return;\n const rot = rotateCW(cur.shape);\n for (const k of KICK_OFFSETS) {\n const p = { x: cur.pos.x + k.x, y: cur.pos.y + k.y };\n if (!collides(board, rot, p)) { cur.shape = rot; cur.pos = p; return; }\n }\n}\n\nfunction hardDrop(): void {\n if (!cur) return;\n const gy = ghostY(board, cur.shape, cur.pos);\n score += (gy - cur.pos.y) * 2;\n cur.pos.y = gy;\n lockAndAdvance();\n}\n\nfunction lockAndAdvance(): void {\n if (!cur) return;\n board = lock(board, cur.shape, cur.pos, cur.color);\n const { grid: nb, cleared } = clearLines(board);\n board = nb;\n if (cleared) { linesCleared += cleared; score += calcScore(cleared, level); level = Math.floor(linesCleared / 10); }\n cur = nxt;\n nxt = makePiece(nextIdx());\n canHold = true;\n if (cur && collides(board, cur.shape, cur.pos)) gameOver = true;\n}\n\nfunction doHold(): void {\n if (!cur || !canHold) return;\n canHold = false;\n const cc = cur.color, cs = cur.shape.map(r => [...r]);\n if (held) {\n const hc = held.color, hs = held.shape;\n cur = makePieceFrom(hs, hc);\n held = { shape: cs, color: cc, pos: { x: 0, y: 0 } };\n } else {\n held = { shape: cs, color: cc, pos: { x: 0, y: 0 } };\n cur = nxt;\n nxt = makePiece(nextIdx());\n }\n}\n\n// ── Input ──\n\nlet keyTimers: Record<string, number> = {};\nconst DAS = 170, ARR = 50;\n\nfunction startRepeat(code: string, fn: () => void): void {\n stopRepeat(code);\n keyTimers[code + \"_d\"] = window.setTimeout(() => { keyTimers[code] = window.setInterval(fn, ARR); }, DAS);\n}\n\nfunction stopRepeat(code: string): void {\n clearTimeout(keyTimers[code + \"_d\"]); clearInterval(keyTimers[code]);\n delete keyTimers[code + \"_d\"]; delete keyTimers[code];\n}\n\nfunction onKeyDown(e: KeyboardEvent): void {\n if (e.repeat) return;\n switch (e.code) {\n case \"ArrowLeft\": e.preventDefault(); move(-1,0); startRepeat(\"ArrowLeft\", ()=>move(-1,0)); break;\n case \"ArrowRight\": e.preventDefault(); move(1,0); startRepeat(\"ArrowRight\",()=>move(1,0)); break;\n case \"ArrowDown\": e.preventDefault(); softDrop = true; break;\n case \"ArrowUp\": case \"KeyX\": e.preventDefault(); tryRotate(); break;\n case \"KeyZ\": e.preventDefault(); tryRotate(); tryRotate(); tryRotate(); break;\n case \"Space\": e.preventDefault(); hardDrop(); break;\n case \"KeyC\": case \"ShiftLeft\": case \"ShiftRight\": e.preventDefault(); doHold(); break;\n case \"KeyP\": case \"Escape\": paused = !paused; break;\n case \"KeyR\": resetGame(); break;\n }\n}\n\nfunction onKeyUp(e: KeyboardEvent): void {\n if (e.code === \"ArrowLeft\" || e.code === \"ArrowRight\") stopRepeat(e.code);\n if (e.code === \"ArrowDown\") softDrop = false;\n}\n\n// ── Render Frame ──\n\nlet canvas2d: HTMLCanvasElement, ctx2d: CanvasRenderingContext2D;\n\nfunction drawFrame(): void {\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.uniform2f(uRes, CANVAS_W, CANVAS_H);\n\n const v: number[] = [], cl: number[] = [];\n\n // Board background\n pushQuad(v, cl, BOARD_PAD, BOARD_PAD, BOARD_W, BOARD_H, [0.05, 0.05, 0.08, 1]);\n\n // Grid lines\n const gc: RGBA = [0.12, 0.12, 0.18, 1];\n for (let c = 0; c <= COLS; c++) pushQuad(v, cl, BOARD_PAD + c * CELL, BOARD_PAD, 1, BOARD_H, gc);\n for (let r = 0; r <= ROWS; r++) pushQuad(v, cl, BOARD_PAD, BOARD_PAD + r * CELL, BOARD_W, 1, gc);\n\n // Locked cells\n for (let r = 0; r < ROWS; r++)\n for (let c = 0; c < COLS; c++)\n if (board[r][c] !== Color.Empty)\n pushBlock(v, cl, BOARD_PAD + c * CELL, BOARD_PAD + r * CELL, CELL, PALETTE[board[r][c]]);\n\n // Ghost\n if (cur) {\n const gy = ghostY(board, cur.shape, cur.pos);\n for (let r = 0; r < cur.shape.length; r++)\n for (let c = 0; c < cur.shape[r].length; c++) {\n if (!cur.shape[r][c]) continue;\n const py = gy + r;\n if (py < 0) continue;\n pushQuad(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL + 1, BOARD_PAD + py * CELL + 1, CELL - 2, CELL - 2, [0.3, 0.3, 0.4, 0.35]);\n }\n\n // Current piece\n for (let r = 0; r < cur.shape.length; r++)\n for (let c = 0; c < cur.shape[r].length; c++) {\n if (!cur.shape[r][c]) continue;\n const py = cur.pos.y + r;\n if (py < 0) continue;\n pushBlock(v, cl, BOARD_PAD + (cur.pos.x + c) * CELL, BOARD_PAD + py * CELL, CELL, PALETTE[cur.color]);\n }\n }\n\n // Sidebar\n const sx = BOARD_PAD + BOARD_W + BOARD_PAD;\n const panelH = 130;\n\n // Next panel\n pushQuad(v, cl, sx, BOARD_PAD, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]);\n if (nxt) {\n const sz = nxt.shape.length, mini = 22;\n const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2;\n const oy = BOARD_PAD + 30;\n for (let r = 0; r < sz; r++)\n for (let c = 0; c < sz; c++)\n if (nxt.shape[r][c]) pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[nxt.color]);\n }\n\n // Hold panel\n const holdY = BOARD_PAD + panelH + 15;\n pushQuad(v, cl, sx, holdY, SIDEBAR_W - 20, panelH, [0.1, 0.1, 0.16, 1]);\n if (held) {\n const sz = held.shape.length, mini = 22;\n const ox = sx + (SIDEBAR_W - 20) / 2 - (sz * mini) / 2;\n const oy = holdY + 30;\n for (let r = 0; r < sz; r++)\n for (let c = 0; c < sz; c++)\n if (held.shape[r][c]) pushBlock(v, cl, ox + c * mini, oy + r * mini, mini, PALETTE[held.color]);\n }\n\n // Upload and draw\n const stride = 6;\n const data = new Float32Array(v.length + cl.length);\n for (let i = 0, vi = 0, ci = 0; i < data.length; i += stride) {\n data[i] = v[vi++]; data[i+1] = v[vi++];\n data[i+2] = cl[ci++]; data[i+3] = cl[ci++]; data[i+4] = cl[ci++]; data[i+5] = cl[ci++];\n }\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);\n const bs = stride * 4;\n gl.enableVertexAttribArray(aPos);\n gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, bs, 0);\n gl.enableVertexAttribArray(aCol);\n gl.vertexAttribPointer(aCol, 4, gl.FLOAT, false, bs, 8);\n gl.drawArrays(gl.TRIANGLES, 0, data.length / stride);\n\n // 2D text overlay\n const ctx = ctx2d;\n ctx.clearRect(0, 0, CANVAS_W, CANVAS_H);\n ctx.font = \"bold 14px 'Segoe UI', Arial, sans-serif\";\n ctx.fillStyle = \"#aaa\";\n ctx.textAlign = \"left\";\n ctx.fillText(\"NEXT\", sx + 8, BOARD_PAD + 18);\n ctx.fillText(\"HOLD\", sx + 8, holdY + 18);\n\n const iy = holdY + panelH + 30;\n ctx.fillStyle = \"#ccc\"; ctx.font = \"bold 13px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"SCORE\", sx + 8, iy);\n ctx.fillStyle = \"#fff\"; ctx.font = \"bold 22px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(score.toLocaleString(), sx + 8, iy + 24);\n\n ctx.fillStyle = \"#ccc\"; ctx.font = \"bold 13px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"LEVEL\", sx + 8, iy + 54);\n ctx.fillStyle = \"#fff\"; ctx.font = \"bold 22px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(String(level + 1), sx + 8, iy + 78);\n\n ctx.fillStyle = \"#ccc\"; ctx.font = \"bold 13px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"LINES\", sx + 8, iy + 108);\n ctx.fillStyle = \"#fff\"; ctx.font = \"bold 22px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(String(linesCleared), sx + 8, iy + 132);\n\n ctx.fillStyle = \"#666\"; ctx.font = \"12px 'Segoe UI', Arial, sans-serif\";\n const help = [\"← → Move\", \"↑ / X Rotate CW\", \"Z Rotate CCW\", \"↓ Soft Drop\", \"Space Hard Drop\", \"C Hold\", \"P Pause\", \"R Restart\"];\n help.forEach((t, i) => ctx.fillText(t, sx + 8, iy + 170 + i * 18));\n\n if (gameOver) {\n ctx.fillStyle = \"rgba(0,0,0,0.7)\";\n ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 40, BOARD_W, 80);\n ctx.textAlign = \"center\";\n ctx.fillStyle = \"#ff4444\"; ctx.font = \"bold 28px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"GAME OVER\", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4);\n ctx.fillStyle = \"#ccc\"; ctx.font = \"16px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"Press R to restart\", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 28);\n ctx.textAlign = \"left\";\n }\n if (paused && !gameOver) {\n ctx.fillStyle = \"rgba(0,0,0,0.6)\";\n ctx.fillRect(BOARD_PAD, BOARD_PAD + BOARD_H / 2 - 30, BOARD_W, 60);\n ctx.textAlign = \"center\";\n ctx.fillStyle = \"#ffdd44\"; ctx.font = \"bold 28px 'Segoe UI', Arial, sans-serif\";\n ctx.fillText(\"PAUSED\", BOARD_PAD + BOARD_W / 2, BOARD_PAD + BOARD_H / 2 + 4);\n ctx.textAlign = \"left\";\n }\n}\n\n// ── Main Loop ──\n\nfunction tick(now: number): void {\n requestAnimationFrame(tick);\n if (gameOver || paused) { drawFrame(); return; }\n\n const interval = softDrop ? Math.min(gravityMs(level), 50) : gravityMs(level);\n if (now - lastTick >= interval) {\n lastTick = now;\n if (softDrop) score += 1;\n if (!move(0, 1)) lockAndAdvance();\n }\n drawFrame();\n}\n\n// ── Bootstrap ──\n\nfunction start(): void {\n const wrapper = document.getElementById(\"game-wrapper\")!;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = CANVAS_W; canvas.height = CANVAS_H;\n canvas.style.display = \"block\"; canvas.style.borderRadius = \"4px\";\n canvas.tabIndex = 0;\n wrapper.appendChild(canvas);\n\n canvas2d = document.createElement(\"canvas\");\n canvas2d.width = CANVAS_W; canvas2d.height = CANVAS_H;\n canvas2d.style.position = \"absolute\"; canvas2d.style.left = \"0\"; canvas2d.style.top = \"0\";\n canvas2d.style.pointerEvents = \"none\";\n wrapper.style.position = \"relative\";\n wrapper.appendChild(canvas2d);\n ctx2d = canvas2d.getContext(\"2d\")!;\n\n initGL(canvas);\n resetGame();\n\n canvas.addEventListener(\"keydown\", onKeyDown);\n canvas.addEventListener(\"keyup\", onKeyUp);\n canvas.focus();\n\n requestAnimationFrame(tick);\n}\n\nif (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", start);\n} else {\n start();\n}\nGEOF","description":"Create single-file Tetris game"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"2e952420-f95b-447f-a600-eb0435347c82"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_a7a77707320c49a7897a5826","type":"tool_result","content":"[rerun: b24]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"036b1b27-4c0e-436d-a4ed-a89a0c010d14","timestamp":"2026-04-07T02:17:27.215Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_2026040710172879403e9fb9774165","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now update the tsconfig and compile:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"32031d7f-4d36-4541-8d8e-df6c771132f7"} +{"type":"assistant","message":{"id":"msg_2026040710172879403e9fb9774165","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_f11a29a8abad48aa8af1465b","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/tsconfig.json << 'EOF'\n{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ES2020\",\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"sourceMap\": false,\n \"outDir\": \"../build\",\n \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"]\n },\n \"files\": [\"src/tetris.ts\"]\n}\nEOF","description":"Update tsconfig for single file"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"45bf8667-2642-471e-819b-abed7262f92e"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_f11a29a8abad48aa8af1465b","type":"tool_result","content":"[rerun: b25]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"939b4bd1-4f91-4663-b326-33161ec79a23","timestamp":"2026-04-07T02:17:37.971Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071017390520ebb712134369","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_c95fd3e2f0424a39a1dd7c77","name":"Bash","input":{"command":"npx tsc 2>&1","description":"Compile TypeScript"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"f2af83d2-6c92-4daa-8885-f967048c9247"} +{"type":"user","message":{"role":"user","content":[{"type":"tool_result","content":"Exit code 2\ntsconfig.json(9,5): error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.\n Visit https://aka.ms/ts6 for migration information.\n[rerun: b26]","is_error":true,"tool_use_id":"call_c95fd3e2f0424a39a1dd7c77"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"699e0c24-15df-4f3c-b410-f4802c7cbe80","timestamp":"2026-04-07T02:17:47.028Z","tool_use_result":"Error: Exit code 2\ntsconfig.json(9,5): error TS5011: The common source directory of 'tsconfig.json' is './src'. The 'rootDir' setting must be explicitly set to this or another path to adjust your output's file layout.\n Visit https://aka.ms/ts6 for migration information.\n[rerun: b26]"} +{"type":"assistant","message":{"id":"msg_2026040710174878dc05ef1aac41cc","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_7433effeddd543e1ac8ed5a1","name":"Bash","input":{"command":"cat > /tmp/loop-bench-718df8zr/tetris/tsconfig.json << 'EOF'\n{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ES2020\",\n \"strict\": true,\n \"noImplicitAny\": true,\n \"removeComments\": true,\n \"sourceMap\": false,\n \"rootDir\": \"./src\",\n \"outDir\": \"../build\",\n \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"]\n },\n \"files\": [\"src/tetris.ts\"]\n}\nEOF","description":"Add rootDir to tsconfig"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"a7d65d9e-24ab-4f50-bdf2-bff8a9adca2d"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_7433effeddd543e1ac8ed5a1","type":"tool_result","content":"[rerun: b27]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"1d742063-b992-4664-822e-ff423ecb32c3","timestamp":"2026-04-07T02:18:00.858Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_2026040710180206ead3380fd94321","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_f30ad7dff1d54d369f4d0308","name":"Bash","input":{"command":"npx tsc 2>&1","description":"Compile TypeScript"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"f6295066-1daf-4c16-8f48-c1f4903574e8"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_f30ad7dff1d54d369f4d0308","type":"tool_result","content":"[rerun: b28]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"f78cbbdf-2fd6-4167-a13b-033fcfe993b5","timestamp":"2026-04-07T02:18:08.908Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071018108a7d42ceb5d441ed","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_2172fcc610cd42aea3a2e702","name":"Bash","input":{"command":"ls -la /tmp/loop-bench-718df8zr/build/ && wc -l /tmp/loop-bench-718df8zr/build/tetris.js","description":"Verify compiled output"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"78474452-1d1a-4aad-9dae-16e737693dd7"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_2172fcc610cd42aea3a2e702","type":"tool_result","content":"total 32\ndrwxr-xr-x 3 root root 4096 Apr 7 04:18 .\ndrwx------ 7 root root 4096 Apr 7 04:17 ..\ndrwxr-xr-x 2 root root 4096 Apr 7 04:17 src\n-rw-r--r-- 1 root root 17256 Apr 7 04:18 tetris.js\n516 /tmp/loop-bench-718df8zr/build/tetris.js\n[rerun: b29]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"e7374a30-f743-438c-9c8c-71d4e3107cf3","timestamp":"2026-04-07T02:18:18.869Z","tool_use_result":{"stdout":"total 32\ndrwxr-xr-x 3 root root 4096 Apr 7 04:18 .\ndrwx------ 7 root root 4096 Apr 7 04:17 ..\ndrwxr-xr-x 2 root root 4096 Apr 7 04:17 src\n-rw-r--r-- 1 root root 17256 Apr 7 04:18 tetris.js\n516 /tmp/loop-bench-718df8zr/build/tetris.js","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071018203e88225c07f44308","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The compiled JS is at `/tmp/loop-bench-718df8zr/build/tetris.js`, but the HTML references `build/tetris.js` relative to the tetris directory. Let me update the HTML to point to the correct path, or move the file.\n\nActually, the HTML is at `/tmp/loop-bench-718df8zr/tetris/index.html` and it references `build/tetris.js` which would look for `/tmp/loop-bench-718df8zr/tetris/build/tetris.js`. But the compiled output is at `/tmp/loop-bench-718df8zr/build/tetris.js`.\n\nI have two options:\n1. Move/copy the compiled file\n2. Update the HTML path\n\nLet me just copy the compiled file to the right place. Or better, adjust the output. Actually the simplest is to just copy the compiled JS to where the HTML expects it.\n\nWait, but since `module: \"ES2020\"`, the output will have `export` statements which won't work in a browser with a plain `<script>` tag. I need to either:\n1. Use `<script type=\"module\">`\n2. Use `module: \"none\"` with no imports\n\nSince I wrote a single file with no imports/exports, the TS compiler shouldn't add any. Let me check the output.\n\nActually, since there are no import/export in the file, TypeScript should treat it as a script, not a module. Let me check.","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"a70d17a9-c261-4368-8556-1076716e06bb"} +{"type":"assistant","message":{"id":"msg_202604071018203e88225c07f44308","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_310143e032c343c785db288f","name":"Bash","input":{"command":"head -10 /tmp/loop-bench-718df8zr/build/tetris.js && echo \"...\" && tail -5 /tmp/loop-bench-718df8zr/build/tetris.js","description":"Check compiled JS for module syntax"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"3b74fc79-2a93-4bc0-aafc-d73cf624abf4"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_310143e032c343c785db288f","type":"tool_result","content":"\"use strict\";\nvar Color;\n(function (Color) {\n Color[Color[\"Empty\"] = 0] = \"Empty\";\n Color[Color[\"Cyan\"] = 1] = \"Cyan\";\n Color[Color[\"Yellow\"] = 2] = \"Yellow\";\n Color[Color[\"Purple\"] = 3] = \"Purple\";\n Color[Color[\"Green\"] = 4] = \"Green\";\n Color[Color[\"Red\"] = 5] = \"Red\";\n Color[Color[\"Blue\"] = 6] = \"Blue\";\n...\n document.addEventListener(\"DOMContentLoaded\", start);\n}\nelse {\n start();\n}\n[rerun: b30]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"35d9d59e-d60e-4367-a29b-833e04fd5064","timestamp":"2026-04-07T02:18:41.353Z","tool_use_result":{"stdout":"\"use strict\";\nvar Color;\n(function (Color) {\n Color[Color[\"Empty\"] = 0] = \"Empty\";\n Color[Color[\"Cyan\"] = 1] = \"Cyan\";\n Color[Color[\"Yellow\"] = 2] = \"Yellow\";\n Color[Color[\"Purple\"] = 3] = \"Purple\";\n Color[Color[\"Green\"] = 4] = \"Green\";\n Color[Color[\"Red\"] = 5] = \"Red\";\n Color[Color[\"Blue\"] = 6] = \"Blue\";\n...\n document.addEventListener(\"DOMContentLoaded\", start);\n}\nelse {\n start();\n}","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_2026040710184275ccfac48f374448","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"No module exports — it's a plain IIFE-style script. Now let me copy it to where the HTML expects it:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"df1c3f1d-281e-4d79-a98f-602aeb7b6264"} +{"type":"assistant","message":{"id":"msg_2026040710184275ccfac48f374448","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_d2b2b474f5e74046a9010cd5","name":"Bash","input":{"command":"cp /tmp/loop-bench-718df8zr/build/tetris.js /tmp/loop-bench-718df8zr/tetris/build/tetris.js","description":"Copy compiled JS to tetris/build/"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"60024153-ab56-477d-b859-0adc890cd160"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_d2b2b474f5e74046a9010cd5","type":"tool_result","content":"[rerun: b31]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"4f66e61f-3b9a-425e-b834-d2c967f037ca","timestamp":"2026-04-07T02:18:49.346Z","tool_use_result":{"stdout":"","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":true}} +{"type":"assistant","message":{"id":"msg_20260407101851629878e9d69b453c","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Let me verify the whole project structure is clean:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"7afb310f-df67-41fc-8ee6-a9c3061c0cad"} +{"type":"assistant","message":{"id":"msg_20260407101851629878e9d69b453c","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_27a5ce8ad32f4597b45e6c09","name":"Bash","input":{"command":"find /tmp/loop-bench-718df8zr/tetris -type f | sort","description":"List all project files"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"e1205178-481e-4548-b024-3093831804e3"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_27a5ce8ad32f4597b45e6c09","type":"tool_result","content":"/tmp/loop-bench-718df8zr/tetris/build/tetris.js\n/tmp/loop-bench-718df8zr/tetris/index.html\n/tmp/loop-bench-718df8zr/tetris/src/board.ts\n/tmp/loop-bench-718df8zr/tetris/src/game.ts\n/tmp/loop-bench-718df8zr/tetris/src/pieces.ts\n/tmp/loop-bench-718df8zr/tetris/src/renderer.ts\n/tmp/loop-bench-718df8zr/tetris/src/shaders.ts\n/tmp/loop-bench-718df8zr/tetris/src/tetris.ts\n/tmp/loop-bench-718df8zr/tetris/src/types.ts\n/tmp/loop-bench-718df8zr/tetris/tsconfig.json\n[rerun: b32]","is_error":false}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"25c19cb5-2d65-4bf9-8943-a2c2ce58beef","timestamp":"2026-04-07T02:18:55.674Z","tool_use_result":{"stdout":"/tmp/loop-bench-718df8zr/tetris/build/tetris.js\n/tmp/loop-bench-718df8zr/tetris/index.html\n/tmp/loop-bench-718df8zr/tetris/src/board.ts\n/tmp/loop-bench-718df8zr/tetris/src/game.ts\n/tmp/loop-bench-718df8zr/tetris/src/pieces.ts\n/tmp/loop-bench-718df8zr/tetris/src/renderer.ts\n/tmp/loop-bench-718df8zr/tetris/src/shaders.ts\n/tmp/loop-bench-718df8zr/tetris/src/tetris.ts\n/tmp/loop-bench-718df8zr/tetris/src/types.ts\n/tmp/loop-bench-718df8zr/tetris/tsconfig.json","stderr":"","interrupted":false,"isImage":false,"noOutputExpected":false}} +{"type":"assistant","message":{"id":"msg_202604071018575e5704e403b24e72","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Good. The main source is `src/tetris.ts` (single-file game), compiled to `build/tetris.js`, loaded by `index.html`. Let me verify the final HTML:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"972e77a9-d3e2-4e82-b7e1-54afdde089d9"} +{"type":"assistant","message":{"id":"msg_202604071018575e5704e403b24e72","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_306f5f6df37c44999cff548d","name":"Read","input":{"file_path":"/tmp/loop-bench-718df8zr/tetris/index.html"}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"d230b135-d562-4600-af7f-958a4e688872"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_306f5f6df37c44999cff548d","type":"tool_result","content":"1\t<!DOCTYPE html>\n2\t<html lang=\"en\">\n3\t<head>\n4\t <meta charset=\"UTF-8\" />\n5\t <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n6\t <title>Tetris – WebGL</title>\n7\t <style>\n8\t * { margin: 0; padding: 0; box-sizing: border-box; }\n9\t body {\n10\t background: #0c0c14;\n11\t display: flex;\n12\t flex-direction: column;\n13\t align-items: center;\n14\t justify-content: center;\n15\t height: 100vh;\n16\t font-family: 'Segoe UI', Arial, sans-serif;\n17\t overflow: hidden;\n18\t }\n19\t h1 {\n20\t color: #e0e0e0;\n21\t font-size: 24px;\n22\t margin-bottom: 12px;\n23\t letter-spacing: 4px;\n24\t text-transform: uppercase;\n25\t }\n26\t #game-wrapper {\n27\t position: relative;\n28\t display: inline-block;\n29\t }\n30\t canvas { outline: none; }\n31\t #game-wrapper canvas + canvas {\n32\t /* text overlay canvas sits on top */\n33\t }\n34\t </style>\n35\t</head>\n36\t<body>\n37\t <h1>Tetris</h1>\n38\t <div id=\"game-wrapper\"></div>\n39\t <script src=\"build/tetris.js\"></script>\n40\t</body>\n41\t</html>\n42\t"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"a14c4611-9d27-461c-8d35-b60406bd9d52","timestamp":"2026-04-07T02:19:03.745Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-718df8zr/tetris/index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Tetris – WebGL</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n background: #0c0c14;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100vh;\n font-family: 'Segoe UI', Arial, sans-serif;\n overflow: hidden;\n }\n h1 {\n color: #e0e0e0;\n font-size: 24px;\n margin-bottom: 12px;\n letter-spacing: 4px;\n text-transform: uppercase;\n }\n #game-wrapper {\n position: relative;\n display: inline-block;\n }\n canvas { outline: none; }\n #game-wrapper canvas + canvas {\n /* text overlay canvas sits on top */\n }\n </style>\n</head>\n<body>\n <h1>Tetris</h1>\n <div id=\"game-wrapper\"></div>\n <script src=\"build/tetris.js\"></script>\n</body>\n</html>\n","numLines":42,"startLine":1,"totalLines":42}}} +{"type":"assistant","message":{"id":"msg_20260407101905fe743571199e4adf","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Now let me verify the compiled JS is correct by checking a few key parts:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"18b8ebf0-8337-43ad-8466-3181a20ed3de"} +{"type":"assistant","message":{"id":"msg_20260407101905fe743571199e4adf","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_a22f7a09130c4aaa8f31d703","name":"Read","input":{"file_path":"/tmp/loop-bench-718df8zr/tetris/build/tetris.js","limit":50}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"5b19036e-317f-441a-affb-2eb79e854605"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_a22f7a09130c4aaa8f31d703","type":"tool_result","content":"1\t\"use strict\";\n2\tvar Color;\n3\t(function (Color) {\n4\t Color[Color[\"Empty\"] = 0] = \"Empty\";\n5\t Color[Color[\"Cyan\"] = 1] = \"Cyan\";\n6\t Color[Color[\"Yellow\"] = 2] = \"Yellow\";\n7\t Color[Color[\"Purple\"] = 3] = \"Purple\";\n8\t Color[Color[\"Green\"] = 4] = \"Green\";\n9\t Color[Color[\"Red\"] = 5] = \"Red\";\n10\t Color[Color[\"Blue\"] = 6] = \"Blue\";\n11\t Color[Color[\"Orange\"] = 7] = \"Orange\";\n12\t})(Color || (Color = {}));\n13\tconst COLS = 10;\n14\tconst ROWS = 20;\n15\tconst CELL = 30;\n16\tconst BOARD_PAD = 20;\n17\tconst BOARD_W = COLS * CELL;\n18\tconst BOARD_H = ROWS * CELL;\n19\tconst SIDEBAR_W = 200;\n20\tconst CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD;\n21\tconst CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD;\n22\tconst PALETTE = {\n23\t [Color.Empty]: [0.08, 0.08, 0.12, 1],\n24\t [Color.Cyan]: [0.0, 0.9, 0.9, 1],\n25\t [Color.Yellow]: [0.95, 0.9, 0.1, 1],\n26\t [Color.Purple]: [0.7, 0.2, 0.9, 1],\n27\t [Color.Green]: [0.1, 0.9, 0.3, 1],\n28\t [Color.Red]: [0.9, 0.2, 0.2, 1],\n29\t [Color.Blue]: [0.2, 0.3, 0.9, 1],\n30\t [Color.Orange]: [0.95, 0.55, 0.1, 1],\n31\t};\n32\tconst SHAPES = [\n33\t { shape: [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], color: Color.Cyan },\n34\t { shape: [[1, 1], [1, 1]], color: Color.Yellow },\n35\t { shape: [[0, 1, 0], [1, 1, 1], [0, 0, 0]], color: Color.Purple },\n36\t { shape: [[0, 1, 1], [1, 1, 0], [0, 0, 0]], color: Color.Green },\n37\t { shape: [[1, 1, 0], [0, 1, 1], [0, 0, 0]], color: Color.Red },\n38\t { shape: [[1, 0, 0], [1, 1, 1], [0, 0, 0]], color: Color.Blue },\n39\t { shape: [[0, 0, 1], [1, 1, 1], [0, 0, 0]], color: Color.Orange },\n40\t];\n41\tconst KICK_OFFSETS = [\n42\t { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 1, y: 0 },\n43\t { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 },\n44\t { x: -2, y: 0 }, { x: 2, y: 0 },\n45\t];\n46\tlet bag = [];\n47\tfunction refillBag() {\n48\t bag = [0, 1, 2, 3, 4, 5, 6];\n49\t for (let i = bag.length - 1; i > 0; i--) {\n50\t const j = Math.floor(Math.random() * (i + 1));"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"7b78ae87-60fa-402e-8027-270c7ee63c11","timestamp":"2026-04-07T02:19:14.161Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-718df8zr/tetris/build/tetris.js","content":"\"use strict\";\nvar Color;\n(function (Color) {\n Color[Color[\"Empty\"] = 0] = \"Empty\";\n Color[Color[\"Cyan\"] = 1] = \"Cyan\";\n Color[Color[\"Yellow\"] = 2] = \"Yellow\";\n Color[Color[\"Purple\"] = 3] = \"Purple\";\n Color[Color[\"Green\"] = 4] = \"Green\";\n Color[Color[\"Red\"] = 5] = \"Red\";\n Color[Color[\"Blue\"] = 6] = \"Blue\";\n Color[Color[\"Orange\"] = 7] = \"Orange\";\n})(Color || (Color = {}));\nconst COLS = 10;\nconst ROWS = 20;\nconst CELL = 30;\nconst BOARD_PAD = 20;\nconst BOARD_W = COLS * CELL;\nconst BOARD_H = ROWS * CELL;\nconst SIDEBAR_W = 200;\nconst CANVAS_W = BOARD_PAD + BOARD_W + BOARD_PAD + SIDEBAR_W + BOARD_PAD;\nconst CANVAS_H = BOARD_PAD + BOARD_H + BOARD_PAD;\nconst PALETTE = {\n [Color.Empty]: [0.08, 0.08, 0.12, 1],\n [Color.Cyan]: [0.0, 0.9, 0.9, 1],\n [Color.Yellow]: [0.95, 0.9, 0.1, 1],\n [Color.Purple]: [0.7, 0.2, 0.9, 1],\n [Color.Green]: [0.1, 0.9, 0.3, 1],\n [Color.Red]: [0.9, 0.2, 0.2, 1],\n [Color.Blue]: [0.2, 0.3, 0.9, 1],\n [Color.Orange]: [0.95, 0.55, 0.1, 1],\n};\nconst SHAPES = [\n { shape: [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0], [0, 0, 0, 0]], color: Color.Cyan },\n { shape: [[1, 1], [1, 1]], color: Color.Yellow },\n { shape: [[0, 1, 0], [1, 1, 1], [0, 0, 0]], color: Color.Purple },\n { shape: [[0, 1, 1], [1, 1, 0], [0, 0, 0]], color: Color.Green },\n { shape: [[1, 1, 0], [0, 1, 1], [0, 0, 0]], color: Color.Red },\n { shape: [[1, 0, 0], [1, 1, 1], [0, 0, 0]], color: Color.Blue },\n { shape: [[0, 0, 1], [1, 1, 1], [0, 0, 0]], color: Color.Orange },\n];\nconst KICK_OFFSETS = [\n { x: 0, y: 0 }, { x: -1, y: 0 }, { x: 1, y: 0 },\n { x: 0, y: -1 }, { x: -1, y: -1 }, { x: 1, y: -1 },\n { x: -2, y: 0 }, { x: 2, y: 0 },\n];\nlet bag = [];\nfunction refillBag() {\n bag = [0, 1, 2, 3, 4, 5, 6];\n for (let i = bag.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1));","numLines":50,"startLine":1,"totalLines":517}}} +{"type":"assistant","message":{"id":"msg_20260407101915ebd9defd83824f41","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"The compiled output looks correct. Let me do a final verification that the complete file is well-formed:"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"2b2bcad2-c6eb-4c42-adf4-15249dccb3a5"} +{"type":"assistant","message":{"id":"msg_20260407101915ebd9defd83824f41","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"tool_use","id":"call_5305d6d299be402bae0f95ca","name":"Read","input":{"file_path":"/tmp/loop-bench-718df8zr/tetris/build/tetris.js","offset":500}}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"3b94b3ba-57ee-4497-8056-8e2cd49e2a89"} +{"type":"user","message":{"role":"user","content":[{"tool_use_id":"call_5305d6d299be402bae0f95ca","type":"tool_result","content":"500\t canvas2d.style.pointerEvents = \"none\";\n501\t wrapper.style.position = \"relative\";\n502\t wrapper.appendChild(canvas2d);\n503\t ctx2d = canvas2d.getContext(\"2d\");\n504\t initGL(canvas);\n505\t resetGame();\n506\t canvas.addEventListener(\"keydown\", onKeyDown);\n507\t canvas.addEventListener(\"keyup\", onKeyUp);\n508\t canvas.focus();\n509\t requestAnimationFrame(tick);\n510\t}\n511\tif (document.readyState === \"loading\") {\n512\t document.addEventListener(\"DOMContentLoaded\", start);\n513\t}\n514\telse {\n515\t start();\n516\t}\n517\t"}]},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"a1334f72-16cf-48a9-a6da-6417ea29ba78","timestamp":"2026-04-07T02:19:20.982Z","tool_use_result":{"type":"text","file":{"filePath":"/tmp/loop-bench-718df8zr/tetris/build/tetris.js","content":" canvas2d.style.pointerEvents = \"none\";\n wrapper.style.position = \"relative\";\n wrapper.appendChild(canvas2d);\n ctx2d = canvas2d.getContext(\"2d\");\n initGL(canvas);\n resetGame();\n canvas.addEventListener(\"keydown\", onKeyDown);\n canvas.addEventListener(\"keyup\", onKeyUp);\n canvas.focus();\n requestAnimationFrame(tick);\n}\nif (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", start);\n}\nelse {\n start();\n}\n","numLines":18,"startLine":500,"totalLines":517}}} +{"type":"assistant","message":{"id":"msg_202604071019222b4b8e2821aa4ec5","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"text","text":"Everything compiles cleanly and the output is correct. Here's a summary of what was built:\n\n---\n\n## Tetris — TypeScript + WebGL\n\n**Project location:** `/tmp/loop-bench-718df8zr/tetris/`\n\n### How to play\n\nOpen `index.html` in a browser (or serve it with any static file server):\n\n```bash\ncd /tmp/loop-bench-718df8zr/tetris && python3 -m http.server 8080\n```\n\nThen visit `http://localhost:8080`.\n\n### Controls\n\n| Key | Action |\n|---|---|\n| ← → | Move left/right |\n| ↑ or X | Rotate clockwise |\n| Z | Rotate counter-clockwise |\n| ↓ | Soft drop (faster fall) |\n| Space | Hard drop (instant) |\n| C / Shift | Hold piece |\n| P / Esc | Pause |\n| R | Restart |\n\n### Architecture\n\n- **`src/tetris.ts`** — Single-file TypeScript source containing all game logic and rendering\n- **`build/tetris.js`** — Compiled JavaScript output\n- **`index.html`** — Entry point that loads the game\n\n### Features implemented\n\n| Feature | Details |\n|---|---|\n| **WebGL rendering** | Custom vertex/fragment shaders drawing colored quads with 3D beveled block styling |\n| **7-bag randomizer** | Each set of 7 tetrominos appears exactly once before reshuffling |\n| **Piece rotation** | Clockwise (↑/X) and counter-clockwise (Z) with wall-kick offsets for 8 positions |\n| **Ghost piece** | Semi-transparent preview showing where the piece will land |\n| **Hold piece** | Store one piece and swap it back later (once per drop) |\n| **Line clearing** | Completed rows are removed, remaining rows collapse down |\n| **Scoring** | NES-style: 40/100/300/1200 × (level+1) for 1/2/3/4 lines; soft drop +1/cell, hard drop +2/cell |\n| **Increasing speed** | Gravity decreases from 800ms down to 30ms across 20 levels; level advances every 10 lines |\n| **DAS/ARR** | Delayed Auto Shift (170ms delay, 50ms repeat) for smooth horizontal movement |\n| **Text overlay** | 2D canvas overlay for score, level, lines, controls help, and game-over/pause screens |"}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","uuid":"0f789bd9-9737-46ef-b84d-45bd23c06a3f"} +{"type":"result","subtype":"success","is_error":false,"duration_ms":861235,"duration_api_ms":847540,"num_turns":39,"result":"Everything compiles cleanly and the output is correct. Here's a summary of what was built:\n\n---\n\n## Tetris — TypeScript + WebGL\n\n**Project location:** `/tmp/loop-bench-718df8zr/tetris/`\n\n### How to play\n\nOpen `index.html` in a browser (or serve it with any static file server):\n\n```bash\ncd /tmp/loop-bench-718df8zr/tetris && python3 -m http.server 8080\n```\n\nThen visit `http://localhost:8080`.\n\n### Controls\n\n| Key | Action |\n|---|---|\n| ← → | Move left/right |\n| ↑ or X | Rotate clockwise |\n| Z | Rotate counter-clockwise |\n| ↓ | Soft drop (faster fall) |\n| Space | Hard drop (instant) |\n| C / Shift | Hold piece |\n| P / Esc | Pause |\n| R | Restart |\n\n### Architecture\n\n- **`src/tetris.ts`** — Single-file TypeScript source containing all game logic and rendering\n- **`build/tetris.js`** — Compiled JavaScript output\n- **`index.html`** — Entry point that loads the game\n\n### Features implemented\n\n| Feature | Details |\n|---|---|\n| **WebGL rendering** | Custom vertex/fragment shaders drawing colored quads with 3D beveled block styling |\n| **7-bag randomizer** | Each set of 7 tetrominos appears exactly once before reshuffling |\n| **Piece rotation** | Clockwise (↑/X) and counter-clockwise (Z) with wall-kick offsets for 8 positions |\n| **Ghost piece** | Semi-transparent preview showing where the piece will land |\n| **Hold piece** | Store one piece and swap it back later (once per drop) |\n| **Line clearing** | Completed rows are removed, remaining rows collapse down |\n| **Scoring** | NES-style: 40/100/300/1200 × (level+1) for 1/2/3/4 lines; soft drop +1/cell, hard drop +2/cell |\n| **Increasing speed** | Gravity decreases from 800ms down to 30ms across 20 levels; level advances every 10 lines |\n| **DAS/ARR** | Delayed Auto Shift (170ms delay, 50ms repeat) for smooth horizontal movement |\n| **Text overlay** | 2D canvas overlay for score, level, lines, controls help, and game-over/pause screens |","stop_reason":"end_turn","session_id":"5d92b0b9-dc3c-406b-a474-e970eca5ae16","total_cost_usd":0.49940099999999993,"usage":{"input_tokens":23547,"cache_creation_input_tokens":0,"cache_read_input_tokens":536000,"output_tokens":17864,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{"glm-5.1":{"inputTokens":23547,"outputTokens":17864,"cacheReadInputTokens":536000,"cacheCreationInputTokens":0,"webSearchRequests":0,"costUSD":0.49940099999999993,"contextWindow":200000,"maxOutputTokens":32000}},"permission_denials":[],"terminal_reason":"completed","fast_mode_state":"off","uuid":"4bc89606-2df8-4fbd-ba2f-3638efe4f7d9"} diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/claude_output.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/claude_output.json @@ -0,0 +1 @@ +{"type":"assistant","message":{"id":"msg_2026040710110110189d5aadb047d8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The user wants me to build a playable Tetris game in TypeScript that renders using WebGL and runs in a browser. Let me plan this out:\n\n1. I need to create an HTML file that loads the game\n2. I need TypeScript files for the game logic and WebGL rendering\n3. Standard Tetris mechanics: piece rotation, line clearing, scoring, increasing speed\n4. Keyboard controls for movement and rotation\n\nLet me think about the architecture:\n\n- `index.html` - Main HTML file\n- `src/game.ts` - Game logic (board, pieces, collision, line clearing, scoring)\n- `src/renderer.ts` - WebGL rendering\n- `src/main.ts` - Entry point, game loop, input handling\n- `tsconfig.json` - TypeScript config\n- `package.json` - For building\n\nActually, to keep it simpler and self-contained, let me create a single TypeScript file that compiles to a bundle, plus an HTML file. Or I could use a few files with a simple build setup.\n\nLet me think about what's most practical. I'll create:\n1. `index.html` - with canvas and UI\n2. `src/tetris.ts` - All game logic and WebGL rendering in one file (or split into a few)\n3. Build with `tsc` and a bundler, or just use esbuild\n\nLet me use esbuild for simplicity. I'll create:\n- `package.json`\n- `tsconfig.json`\n- `index.html`\n- `src/main.ts` - entry point with game loop and input\n- `src/board.ts` - game board logic\n- `src/pieces.ts` - tetromino definitions\n- `src/renderer.ts` - WebGL renderer\n- `src/shaders.ts` - GLSL shaders\n\nActually, let me keep it manageable. I'll create a few well-organized files.\n\nLet me think about the WebGL rendering approach:\n- Draw the board as a grid of colored quads\n- Each cell is a quad made of 2 triangles\n- Use instanced rendering or just batch all the vertices\n- Simple vertex + fragment shaders\n\nFor the game mechanics:\n- 10x20 board\n- 7 standard tetrominoes (I, O, T, S, Z, J, L)\n- SRS rotation system (or simplified)\n- Wall kicks\n- Line clearing with animation\n- Scoring: standard NES-style or guideline\n- Increasing speed (gravity increases with level)\n- Next piece preview\n- Ghost piece (shadow showing where piece will land)\n\nLet me design the WebGL renderer:\n- Orthographic projection\n- Draw filled rectangles for each cell\n- Use vertex buffer with all cell positions and colors\n- Update buffer each frame\n\nLet me start coding. I'll use esbuild for bundling.\n\nLet me create a clean, well-structured project:\n\n```\ntetris/\n package.json\n tsconfig.json\n index.html\n src/\n main.ts\n game.ts\n renderer.ts\n```\n\nActually, let me think about this more carefully. I want this to be a really nice Tetris game. Let me plan the WebGL rendering:\n\nThe board is 10 wide x 20 visible (+ 2-4 hidden rows at top).\n\nFor rendering, I'll:\n1. Use a single vertex buffer that I update each frame\n2. Each cell = 2 triangles = 6 vertices, each vertex has position (x,y) and color (r,g,b)\n3. Draw everything in one draw call\n\nFor the UI overlay (score, level, next piece), I can either:\n- Render them with WebGL too\n- Use HTML overlays on top of the canvas\n\nI'll use HTML for the UI elements (score, level, next piece display) and WebGL for the game board. This is cleaner.\n\nLet me think about the game state:\n- board: number[][] (10x24, where 0=empty, 1-7=colors)\n- currentPiece: { type, rotation, x, y }\n- nextPiece: type\n- score, level, lines\n- dropTimer, dropInterval\n- gameState: 'playing' | 'paused' | 'gameover'\n\nTetromino definitions - I'll store them as 4 rotations each, using a 4x4 or bounding box grid.\n\nStandard colors:\n- I: cyan\n- O: yellow\n- T: purple\n- S: green\n- Z: red\n- J: blue\n- L: orange\n\nLet me code this up now. I'll make it comprehensive and polished.\n\nFor the WebGL shaders:\n- Vertex shader: takes position and color attributes, passes color to fragment\n- Fragment shader: outputs the color, maybe with some slight shading for a 3D effect\n\nLet me also add:\n- Ghost piece (transparent version showing where piece will land)\n- Grid lines\n- Border around the playfield\n- Next piece preview\n- Hold piece\n- Hard drop (space bar)\n- Soft drop (down arrow)\n- Left/right movement\n- Rotation (up arrow or Z/X)\n\nControls:\n- Left/Right arrows: move piece\n- Down arrow: soft drop\n- Up arrow or X: rotate clockwise\n- Z: rotate counter-clockwise\n- Space: hard drop\n- P: pause\n\nScoring (NES-style):\n- 1 line: 40 * (level + 1)\n- 2 lines: 100 * (level + 1)\n- 3 lines: 300 * (level + 1)\n- 4 lines: 1200 * (level + 1)\n\nLevel increases every 10 lines.\nSpeed: dropInterval = max(100, 1000 - (level * 100)) or something like that.\n\nActually, let me use guideline scoring:\n- Single: 100 * level\n- Double: 300 * level\n- Triple: 500 * level\n- Tetris: 800 * level\n- Soft drop: 1 per cell\n- Hard drop: 2 per cell\n\nSpeed curve (guideline-like):\nLevel 0: 800ms, Level 1: 720ms, ... Level 9: 100ms, Level 10-12: 80ms, etc.\n\nLet me simplify: gravity = frames per gridcell. At 60fps:\nLevel 0: 48 frames (800ms)\nLevel 1: 43\n...\nLevel 9: 6\nLevel 10-12: 5\n...\n\nActually, let me just use a simple formula and not overcomplicate.\n\nOK let me start writing the code. I'll aim for clean, well-organized TypeScript.\n\nLet me structure the files:\n\n1. `index.html` - HTML with canvas and UI overlay\n2. `src/main.ts` - Entry point, initializes everything\n3. `src/game.ts` - Game logic class\n4. `src/renderer.ts` - WebGL renderer class\n5. `src/types.ts` - Type definitions\n6. `src/shaders.ts` - Shader source code\n\nActually, let me combine types into the files that use them. And shaders can be string constants in the renderer.\n\nLet me write the code:\n\n### types.ts\n```typescript\nexport type PieceType = 'I' | 'O' | 'T' | 'S' | 'Z' | 'J' | 'L';\n\nexport interface Piece {\n type: PieceType;\n rotation: number;\n x: number;\n y: number;\n}\n\nexport type GameState = 'playing' | 'paused' | 'gameover' | 'start';\n\nexport interface CellData {\n filled: boolean;\n color: [number, number, number];\n}\n```\n\n### game.ts\nThe game logic. Board is 10 columns x 24 rows (20 visible + 4 hidden). Pieces spawn at the top.\n\nI'll use SRS rotation data. Let me define the tetrominoes.\n\nStandard SRS piece data (using offsets from a reference point):\n\nI-piece rotations (in a 4x4 grid):\n```\nRotation 0: Rotation 1: Rotation 2: Rotation 3:\n. . . . . . X . . . . . . X . .\nX X X X . . X . . . . . . X . .\n. . . . . . X . X X X X . X . .\n. . . . . . X . . . . . . . . .\n```\n\nActually, let me just store the cells as coordinate arrays for each rotation. That's simpler.\n\nLet me define each piece as an array of 4 rotations, where each rotation is an array of 4 [row, col] coordinates.\n\nActually, the standard way to do this in Tetris implementations is to store the piece as a small grid and rotate it. Let me use coordinate offsets relative to the piece's position.\n\nHere are the SRS piece definitions:\n\n```\nI:\n0: [(0,0),(0,1),(0,2),(0,3)] -- horizontal at row 0\n1: [(0,0),(1,0),(2,0),(3,0)] -- wait, this isn't right for SRS\n```\n\nLet me just use the standard representation. I'll define each piece as a matrix and rotate it.\n\nActually, the simplest approach: define each piece as a 2D boolean array for rotation 0, then compute other rotations by rotation. But for accuracy, let me just hardcode all 4 rotations for each piece.\n\nLet me use the SRS definitions. The pieces are defined in a grid, and I'll store the cell coordinates for each rotation state.\n\nFor the I piece (4x4 bounding box):\nState 0: (1,0),(1,1),(1,2),(1,3)\nState 1: (0,2),(1,2),(2,2),(3,2)\nState 2: (2,0),(2,1),(2,2),(2,3)\nState 3: (0,1),(1,1),(2,1),(3,1)\n\nFor the O piece (3x3 bounding box, but it doesn't rotate):\nState 0-3: (0,1),(0,2),(1,1),(1,2)\n\nFor the T piece (3x3 bounding box):\nState 0: (0,1),(1,0),(1,1),(1,2)\nState 1: (0,1),(1,1),(1,2),(2,1)\nState 2: (1,0),(1,1),(1,2),(2,1)\nState 3: (0,1),(1,0),(1,1),(2,1)\n\nFor the S piece (3x3):\nState 0: (0,1),(0,2),(1,0),(1,1)\nState 1: (0,0),(1,0),(1,1),(2,1)\nState 2: (1,1),(1,2),(2,0),(2,1)\nState 3: (0,0),(1,0),(1,1),(2,1)\n\nHmm, I'm getting confused. Let me just define them as small grids and rotate programmatically.\n\nActually, the cleanest approach: define each piece as a list of (row, col) offsets for rotation 0, then compute rotations 1-3 by rotating 90° clockwise each time.\n\nRotation by 90° clockwise: (r, c) -> (c, -r) -> then normalize to have min row/col >= 0.\n\nWait, let me think again. If I have a piece in a bounding box of size N, rotation 90° CW maps (r, c) to (c, N-1-r).\n\nSo for a 3x3 box, (r, c) -> (c, 2-r)\nFor a 4x4 box, (r, c) -> (c, 3-r)\n\nLet me define the pieces for rotation 0 and compute the rest.\n\nI piece (4x4):\n```\n. . . .\nX X X X\n. . . .\n. . . .\n```\nCells: (1,0), (1,1), (1,2), (1,3)\n\nO piece (2x2 effectively, but I'll use 2x2):\n```\nX X\nX X\n```\nCells: (0,0), (0,1), (1,0), (1,1)\n\nT piece (3x3):\n```\n. X .\nX X X\n. . .\n```\nCells: (0,1), (1,0), (1,1), (1,2)\n\nS piece (3x3):\n```\n. X X\nX X .\n. . .\n```\nCells: (0,1), (0,2), (1,0), (1,1)\n\nZ piece (3x3):\n```\nX X .\n. X X\n. . .\n```\nCells: (0,0), (0,1), (1,1), (1,2)\n\nJ piece (3x3):\n```\nX . .\nX X X\n. . .\n```\nCells: (0,0), (1,0), (1,1), (1,2)\n\nL piece (3x3):\n```\n. . X\nX X X\n. . .\n```\nCells: (0,2), (1,0), (1,1), (1,2)\n\nOK, now for rotation. I'll compute rotation states at initialization.\n\nFor 3x3 pieces, CW rotation: (r, c) -> (c, 2-r)\nFor 4x4 pieces (I), CW rotation: (r, c) -> (c, 3-r)\nFor O piece: no change\n\nThis approach is clean. Let me implement it.\n\nFor SRS wall kicks, I'll implement the standard wall kick data. The wall kick tests for J, L, S, T, Z pieces use one table, and I piece uses another.\n\nWall kick data (offsets to try when rotation fails):\nFor JLSTZ pieces, when rotating from state R to new state:\nTest 1: (0, 0) - try without kick\nTest 2: depends on rotation transition\n...\n\nThe SRS wall kick data is:\n\nFrom 0->1: (0,0), (-1,0), (-1,+1), (0,-2), (-1,-2)\nFrom 1->0: (0,0), (+1,0), (+1,-1), (0,+2), (+1,+2)\nFrom 1->2: (0,0), (+1,0), (+1,-1), (0,+2), (+1,+2)\nFrom 2->1: (0,0), (-1,0), (-1,+1), (0,-2), (-1,-2)\nFrom 2->3: (0,0), (+1,0), (+1,+1), (0,-2), (+1,-2)\nFrom 3->2: (0,0), (-1,0), (-1,-1), (0,+2), (-1,+2)\nFrom 3->0: (0,0), (-1,0), (-1,-1), (0,+2), (-1,+2)\nFrom 0->3: (0,0), (+1,0), (+1,+1), (0,-2), (+1,-2)\n\nWait, I need to double-check the SRS wall kick data. The standard uses (x, y) where x is horizontal and y is vertical (positive = up). But in my coordinate system, rows increase downward. Let me be careful.\n\nActually, in the SRS specification, the wall kick data uses (column offset, row offset) where positive column is right and positive row is up. In my system, rows increase downward, so I need to negate the row component.\n\nLet me look up the exact SRS wall kick data:\n\nJLSTZ wall kick data:\n0->R: ( 0, 0), (-1, 0), (-1,+1), ( 0,-2), (-1,-2)\nR->2: ( 0, 0), (+1, 0), (+1,-1), ( 0,+2), (+1,+2)\n2->L: ( 0, 0), (+1, 0), (+1,+1), ( 0,-2), (+1,-2)\nL->0: ( 0, 0), (-1, 0), (-1,-1), ( 0,+2), (-1,+2)\n\nWhere the format is (x_offset, y_offset) with positive x = right, positive y = up.\n\nIn my coordinate system (col = x, row increases downward):\nx_offset stays the same, y_offset gets negated.\n\nSo:\n0->R: (0,0), (-1,0), (-1,-1), (0,+2), (-1,+2)\nR->2: (0,0), (+1,0), (+1,+1), (0,-2), (+1,-2)\n2->L: (0,0), (+1,0), (+1,-1), (0,+2), (+1,+2)\nL->0: (0,0), (-1,0), (-1,+1), (0,-2), (-1,-2)\n\nHmm wait, the SRS uses \"0, R, 2, L\" for the rotation states. 0 = spawn state, R = right (CW from 0), 2 = 180°, L = left (CCW from 0).\n\nAnd for the I piece:\n0->R: ( 0, 0), (-2, 0), (+1, 0), (-2,-1), (+1,+2)\nR->2: ( 0, 0), (-1, 0), (+2, 0), (-1,+2), (+2,-1)\n2->L: ( 0, 0), (+2, 0), (-1, 0), (+2,+1), (-1,-2)\nL->0: ( 0, 0), (+1, 0), (-2, 0), (+1,-2), (-2,+1)\n\nConverting y offsets to my coordinate system (negate):\n0->R: (0,0), (-2,0), (+1,0), (-2,+1), (+1,-2)\nR->2: (0,0), (-1,0), (+2,0), (-1,-2), (+2,+1)\n2->L: (0,0), (+2,0), (-1,0), (+2,-1), (-1,+2)\nL->0: (0,0), (+1,0), (-2,0), (+1,+2), (-2,-1)\n\nThis is getting complex. Let me simplify the implementation. I'll hardcode the wall kick data properly.\n\nOK let me just write the code. I'll be thorough but practical.\n\nLet me think about the WebGL rendering more carefully.\n\nThe canvas will show:\n- The playfield (10x20 grid of cells)\n- Each cell can be empty (dark background) or filled with a color\n- Grid lines separating cells\n- A border around the playfield\n- Ghost piece (semi-transparent)\n- Current piece\n\nFor the WebGL approach:\n1. Set up an orthographic projection that maps to pixel coordinates (or normalized coordinates)\n2. Each frame, build a vertex array for all the quads to draw\n3. Each vertex has: position (x, y), color (r, g, b, a)\n4. Draw everything with gl.TRIANGLES\n\nI'll also add some visual polish:\n- Slight gradient/shading on blocks to give a 3D effect\n- Flashing effect when lines are cleared\n- Smooth animations\n\nActually, for a clean implementation, let me:\n1. Use a coordinate system where the playfield is centered in the canvas\n2. Each cell is a unit square\n3. Render grid lines as thin quads\n4. Render filled cells as quads with the piece color\n5. Render the ghost piece with alpha\n\nFor the vertex format, I'll use interleaved data: x, y, r, g, b, a per vertex.\n\nThe shaders:\n```glsl\n// Vertex shader\nattribute vec2 aPosition;\nattribute vec4 aColor;\nuniform vec2 uResolution;\nvarying vec4 vColor;\n\nvoid main() {\n vec2 clipSpace = (aPosition / uResolution) * 2.0 - 1.0;\n gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);\n vColor = aColor;\n}\n\n// Fragment shader\nprecision mediump float;\nvarying vec4 vColor;\nvoid main() {\n gl_FragColor = vColor;\n}\n```\n\nWait, but I want to add some visual effects to the blocks. Maybe a slight border/bevel effect. I could pass UV coordinates within each cell and use the fragment shader to create the bevel effect.\n\nLet me enhance: each vertex has x, y, u, v, r, g, b, a\nWhere u, v are 0-1 within the cell (0,0 = top-left, 1,1 = bottom-right)\n\nThen in the fragment shader, I can create a bevel effect:\n```glsl\nfloat border = 0.1;\nfloat bevel = 0.15;\nvec4 color = vColor;\nif (u < border || v < border || u > 1.0-border || v > 1.0-border) {\n color *= 0.5; // dark border\n} else if (u < border+bevel && v < border+bevel) {\n // top-left highlight (already default brightness)\n} else if (u > 1.0-border-bevel && v > 1.0-border-bevel) {\n color *= 0.7; // bottom-right shadow\n}\n```\n\nThis would give a nice 3D block look. Let me implement this.\n\nActually, let me simplify the bevel effect. I'll use a cleaner approach:\n\n```glsl\n// Inside the cell (u,v from 0 to 1)\nfloat edge = 0.08;\nfloat bevel = 0.15;\nvec3 col = vColor.rgb;\n\n// Dark border around edges\nfloat borderFactor = smoothstep(0.0, edge, u) * smoothstep(1.0, 1.0-edge, u) *\n smoothstep(0.0, edge, v) * smoothstep(1.0, 1.0-edge, v);\n\n// Highlight on top-left bevel\nfloat highlight = smoothstep(edge+bevel, edge, u) * borderFactor +\n smoothstep(edge+bevel, edge, v) * borderFactor;\n\n// Shadow on bottom-right\nfloat shadow = smoothstep(1.0-edge-bevel, 1.0-edge, u) * borderFactor +\n smoothstep(1.0-edge-bevel, 1.0-edge, v) * borderFactor;\n\ncol = col * (0.6 + 0.4 * borderFactor) + vec3(0.2) * highlight - vec3(0.15) * shadow;\n```\n\nHmm, this is getting complicated. Let me keep it simple but visually appealing. I'll add a basic bevel effect.\n\nLet me now plan the full implementation and start coding.\n\nFiles I'll create:\n1. `package.json` - with esbuild\n2. `tsconfig.json`\n3. `index.html`\n4. `src/main.ts` - Entry point\n5. `src/game.ts` - Game logic\n6. `src/renderer.ts` - WebGL renderer\n\nLet me write them all out. This will be substantial code but I want it to be good.\n\nLet me think about the structure of renderer.ts more carefully.\n\nThe renderer needs to:\n1. Initialize WebGL context\n2. Compile shaders\n3. Set up buffers\n4. Each frame:\n a. Clear the canvas\n b. Build vertex data for all quads (board cells, current piece, ghost piece, grid, border)\n c. Upload vertex data to GPU\n d. Draw\n\nFor the vertex data, I'll pre-allocate a Float32Array that's large enough.\n\nEach vertex: x, y, u, v, r, g, b, a = 8 floats\nEach quad: 6 vertices (2 triangles) = 48 floats\nMax quads: let's say 300 (board is 200 cells + pieces + grid + border) -> 14400 floats\n\nThat's very manageable.\n\nLet me write the code now. I'll be comprehensive.\n\nActually, let me reconsider the project structure. Since I want this to be easy to build and run, I'll use a simple setup:\n\n```json\n{\n \"scripts\": {\n \"build\": \"esbuild src/main.ts --bundle --outfile=public/bundle.js\",\n \"dev\": \"esbuild src/main.ts --bundle --outfile=public/bundle.js --watch --servedir=public\"\n }\n}\n```\n\nWait, but index.html should reference the bundle. Let me put index.html in public/.\n\nActually, let me just put everything flat:\n- `index.html` at root\n- `src/main.ts`, `src/game.ts`, `src/renderer.ts`\n- Build outputs `bundle.js` at root\n\nOK let me just start writing the code.\n\nLet me think about the canvas sizing. I want the playfield to be nicely sized. The playfield is 10x20. If each cell is 30px, that's 300x600. Plus side panels for score and next piece. Total canvas: maybe 500x640 or so. I'll make it responsive but start with a fixed size.\n\nActually, let me make the canvas fill more of the screen and have the game centered. The playfield will be in the center with score/next piece on the right side, all rendered in WebGL.\n\nHmm, rendering text in WebGL is complex. Let me use HTML overlays for text (score, level, next piece info) and use the canvas only for the game board. This is much cleaner.\n\nLet me design the HTML:\n```html\n<div id=\"game-container\">\n <div id=\"left-panel\">\n <div>HOLD</div>\n <canvas id=\"hold-canvas\"></canvas>\n </div>\n <canvas id=\"game-canvas\"></canvas>\n <div id=\"right-panel\">\n <div>NEXT</div>\n <canvas id=\"next-canvas\"></canvas>\n <div>SCORE: <span id=\"score\">0</span></div>\n <div>LEVEL: <span id=\"level\">0</span></div>\n <div>LINES: <span id=\"lines\">0</span></div>\n </div>\n</div>\n```\n\nActually, let me simplify. I'll have one main canvas for the playfield, and use HTML for everything else. The next piece and hold piece can be small canvases or I can draw them in the main canvas.\n\nLet me go with a single canvas approach for simplicity. The canvas will have the playfield on the left portion and info (next piece, score, etc.) drawn with WebGL too. For text, I'll overlay HTML elements.\n\nOr... let me just use a simple approach: one canvas for the game, HTML for the UI. The canvas handles the playfield, and small canvases for next/hold pieces.\n\nActually, the simplest approach that looks good: single large canvas that renders everything including the playfield, side panels with colored rectangles for pieces, etc. For text, use HTML overlays positioned on top.\n\nLet me go with: HTML for text UI, canvas for all the graphical game elements. I'll position HTML elements absolutely over the canvas.\n\nOK I think I'm overcomplicating this. Let me just write the code.\n\nFinal plan:\n- Single canvas, sized to fit the game\n- HTML elements for score, level, lines, next piece label\n- WebGL renders: playfield grid, filled cells, current piece, ghost piece, next piece preview, border\n- The canvas will be sized so the playfield is on the left and there's space on the right for next piece\n\nCanvas layout:\n- Playfield: 10 cells wide, 20 cells tall, starting at some offset\n- Cell size: 30px\n- Playfield: 300x600\n- Next piece area: to the right of the playfield\n- Total canvas: ~440x660 or so\n\nActually let me make it adaptive. Let me calculate the cell size based on the window.\n\nOK let me just start writing. I'll make it work first, then we can polish.\n\nLet me define the cell size as a constant and build from there.\n\nLet me write all the files now.\n\n### package.json\n```json\n{\n \"name\": \"tetris-webgl\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"scripts\": {\n \"build\": \"esbuild src/main.ts --bundle --outfile=dist/bundle.js --minify\",\n \"dev\": \"esbuild src/main.ts --bundle --outfile=dist/bundle.js --watch --servedir=.\"\n },\n \"devDependencies\": {\n \"esbuild\": \"^0.20.0\",\n \"typescript\": \"^5.3.0\"\n }\n}\n```\n\nWait, I don't need `dist/`. Let me just output to `bundle.js` and reference it from `index.html`.\n\nActually, for dev server to work properly with esbuild's `--servedir`, let me structure it as:\n- `index.html` at root references `bundle.js`\n- Build outputs `bundle.js` at root\n\n```json\n{\n \"scripts\": {\n \"build\": \"esbuild src/main.ts --bundle --outfile=bundle.js\",\n \"dev\": \"esbuild src/main.ts --bundle --outfile=bundle.js --watch --servedir=.\"\n }\n}\n```\n\n### tsconfig.json\n```json\n{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"strict\": true,\n \"outDir\": \"./dist\",\n \"rootDir\": \"./src\",\n \"esModuleInterop\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src/**/*\"]\n}\n```\n\n### index.html\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Tetris WebGL</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n background: #1a1a2e;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n font-family: 'Segoe UI', monospace;\n color: #eee;\n overflow: hidden;\n }\n #game-wrapper {\n display: flex;\n gap: 20px;\n align-items: flex-start;\n }\n #canvas-container {\n position: relative;\n }\n canvas {\n display: block;\n border: 2px solid #444;\n border-radius: 4px;\n }\n .side-panel {\n width: 160px;\n display: flex;\n flex-direction: column;\n gap: 15px;\n }\n .panel-box {\n background: rgba(255,255,255,0.05);\n border: 1px solid #333;\n border-radius: 6px;\n padding: 12px;\n }\n .panel-box h3 {\n font-size: 12px;\n color: #888;\n text-transform: uppercase;\n letter-spacing: 2px;\n margin-bottom: 8px;\n }\n .panel-box .value {\n font-size: 24px;\n font-weight: bold;\n }\n canvas.preview {\n width: 120px;\n height: 120px;\n }\n #controls {\n font-size: 11px;\n color: #666;\n line-height: 1.8;\n }\n #start-screen, #gameover-screen {\n position: absolute;\n top: 0; left: 0; right: 0; bottom: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n background: rgba(0,0,0,0.8);\n z-index: 10;\n }\n #start-screen h1, #gameover-screen h1 {\n font-size: 48px;\n margin-bottom: 20px;\n }\n .hidden { display: none !important; }\n button {\n padding: 12px 30px;\n font-size: 18px;\n background: #e94560;\n color: white;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n button:hover {\n background: #ff6b81;\n }\n </style>\n</head>\n<body>\n <div id=\"game-wrapper\">\n <div id=\"canvas-container\">\n <canvas id=\"game-canvas\"></canvas>\n <div id=\"start-screen\">\n <h1>TETRIS</h1>\n <button id=\"start-btn\">START GAME</button>\n </div>\n <div id=\"gameover-screen\" class=\"hidden\">\n <h1>GAME OVER</h1>\n <p id=\"final-score\"></p>\n <button id=\"restart-btn\">PLAY AGAIN</button>\n </div>\n </div>\n <div class=\"side-panel\">\n <div class=\"panel-box\">\n <h3>Next</h3>\n <canvas id=\"next-canvas\" class=\"preview\" width=\"120\" height=\"120\"></canvas>\n </div>\n <div class=\"panel-box\">\n <h3>Score</h3>\n <div class=\"value\" id=\"score\">0</div>\n </div>\n <div class=\"panel-box\">\n <h3>Level</h3>\n <div class=\"value\" id=\"level\">1</div>\n </div>\n <div class=\"panel-box\">\n <h3>Lines</h3>\n <div class=\"value\" id=\"lines\">0</div>\n </div>\n <div class=\"panel-box\" id=\"controls\">\n <h3>Controls</h3>\n ← → Move<br>\n ↓ Soft Drop<br>\n ↑ Rotate CW<br>\n Z Rotate CCW<br>\n Space Hard Drop<br>\n P Pause\n </div>\n </div>\n </div>\n <script src=\"bundle.js\"></script>\n</body>\n</html>\n```\n\nNow for the TypeScript code. Let me write the game logic first.\n\n### src/game.ts\n\n```typescript\nexport type PieceType = 0 | 1 | 2 | 3 | 4 | 5 | 6;\n// 0=I, 1=O, 2=T, 3=S, 4=Z, 5=J, 6=L\n\nexport const PIECE_TYPES = 7;\n\nconst BOARD_WIDTH = 10;\nconst BOARD_HEIGHT = 24; // 4 hidden rows at top\nconst VISIBLE_HEIGHT = 20;\n\n// Piece colors as RGB (0-1)\nexport const PIECE_COLORS: [number, number, number][] = [\n [0, 0.9, 0.9], // I - cyan\n [0.9, 0.9, 0], // O - yellow\n [0.7, 0, 0.9], // T - purple\n [0, 0.9, 0], // S - green\n [0.9, 0, 0], // Z - red\n [0, 0, 0.9], // J - blue\n [0.9, 0.5, 0], // L - orange\n];\n\n// Piece shapes - each rotation is an array of [row, col] offsets\n// Defined for rotation state 0, others computed\n\nconst PIECE_SHAPES: number[][][] = [\n // I piece\n [\n [0, 0], [0, 1], [0, 2], [0, 3]\n ],\n // O piece\n [\n [0, 0], [0, 1], [1, 0], [1, 1]\n ],\n // T piece\n [\n [0, 1], [1, 0], [1, 1], [1, 2]\n ],\n // S piece\n [\n [0, 1], [0, 2], [1, 0], [1, 1]\n ],\n // Z piece\n [\n [0, 0], [0, 1], [1, 1], [1, 2]\n ],\n // J piece\n [\n [0, 0], [1, 0], [1, ","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d13072c-db37-4f2f-a05b-d00d1f108a49","uuid":"51b14e37-2adb-45f5-9623-5ccdcf3b4588"} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/claude_stderr.log b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/claude_stderr.log diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/eval_results.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/eval_results.json @@ -0,0 +1,134 @@ +{ + "structural": { + "pass": false, + "checks": [ + { + "name": "entry_point_exists", + "pass": false, + "detail": "no index.html found in workspace root, dist/, or public/" + }, + { + "name": "package_json_exists", + "pass": true, + "detail": "package.json found" + }, + { + "name": "build_succeeds", + "pass": true, + "detail": "no build script defined (static project)" + }, + { + "name": "typescript_compiles", + "pass": false, + "detail": "TypeScript files found but no tsconfig.json" + } + ], + "score": 0.5 + }, + "quality": { + "lint": { + "pass": true, + "errors": 0, + "warnings": 0 + }, + "typecheck": { + "pass": false, + "error": "no tsconfig.json" + }, + "performance": { + "pass": true, + "bundle_size_bytes": 0, + "size_under_512kb": true + }, + "score": 0.67 + }, + "code_analysis": { + "files": { + "total": 6, + "code": 4, + "docs": 0, + "unnecessary": 0, + "unnecessary_list": [] + }, + "lines_of_code": 596, + "dependencies": { + "production": 0, + "dev": 4, + "total": 4 + }, + "complexity": "moderate", + "console_logs": 0, + "magic_numbers": { + "count": 37, + "excessive": true + }, + "function_length": { + "count": 16, + "average": 6.6, + "max": 16, + "long_functions": 0 + }, + "max_nesting_depth": 5, + "global_declarations": 0, + "naming": { + "dominant_style": "camelCase", + "consistency_pct": 100.0, + "camel_case": 141, + "snake_case": 0 + }, + "error_handling": { + "try_catch_blocks": 4, + "has_error_handling": true + }, + "comments": { + "comment_lines": 70, + "source_lines": 444, + "ratio_pct": 15.8 + }, + "separation_of_concerns": { + "verdict": "mixed", + "files_with_rendering": 2, + "files_with_logic": 1, + "files_with_both": 1 + }, + "html_validation": { + "valid": false, + "errors": 0 + }, + "duplication_percentage": 0.0, + "score": 0.55 + }, + "transcript_analysis": { + "total_events": 4, + "tool_calls": { + "total": 0, + "bash": 0, + "write": 0, + "edit": 0, + "read": 0 + }, + "wasted_turns": { + "total": 0, + "docs": 0, + "ascii_art": 0, + "server_starts": 0 + }, + "errors_encountered": 0, + "thinking_blocks": 1, + "text_blocks": 0, + "productivity_ratio": 0, + "self_tested": false, + "score": 1.0 + }, + "gameplay_bot": { + "pass": false, + "score": 0, + "error": "no HTML files in workspace - game was not built" + }, + "outcome_score": 0.0, + "score": 0.0, + "sonarqube": { + "error": "SonarQube not running at localhost:9000", + "score": 0 + } +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/meta.json b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/meta.json @@ -0,0 +1,40 @@ +{ + "model": "glm-5.1", + "effort": "high", + "prompt_style": "simple", + "language": "typescript", + "human_language": "en", + "tool_read": "on", + "tool_write": "on", + "tool_edit": "on", + "tool_glob": "on", + "tool_grep": "on", + "linter": "on", + "playwright": "off", + "context_file": "none", + "web_search": "on", + "max_budget": "low", + "tests_provided": "none", + "strategy": "none", + "design_guidance": "none", + "architecture": "none", + "error_checking": "none", + "context_noise": "clean", + "renderer": "webgl", + "provider": "zai", + "task": "tetris", + "actual_model": "glm-5.1", + "cell_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on", + "runs_per_cell": 3, + "timeout_seconds": 1200, + "max_budget_usd": 2.0, + "run_id": "tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3", + "short_id": "31d60e5a", + "short_cell_id": "cd8ad131", + "run_number": 3, + "claude_version": "2.1.92 (Claude Code)", + "started_at": "2026-04-07T02:10:57.135222+00:00", + "wall_time_seconds": 1200, + "exit_code": 124, + "completed_at": "2026-04-07T02:31:00.258065+00:00" +} +\ No newline at end of file diff --git a/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/transcript.jsonl b/results/runs/tetris_arch=none_ctx=none_noise=clean_dsgn=none_eff=high_echk=none_hlang=en_lang=ts_lint=on_budget=low_model=glm51_pw=off_prompt=simple_prov=zai_rndr=webgl_strat=none_tst=none_tedit=on_tglob=on_tgrep=on_tread=on_twrite=on_web=on_run3/transcript.jsonl @@ -0,0 +1,4 @@ +{"type": "harness", "subtype": "config", "model": "glm-5.1", "effort": "high", "tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep", "WebSearch", "WebFetch"], "max_budget_usd": 2.0, "timeout_seconds": 1200, "task": "tetris", "language": "typescript", "prompt_style": "simple"} +{"type": "user", "subtype": "prompt", "message": {"role": "user", "content": "Build a playable Tetris game that runs in a browser. It should have all the standard mechanics: piece rotation, line clearing, scoring, and increasing speed. Use keyboard controls for movement and rotation.\n\n\nUse TypeScript.\n\nRender the game using WebGL."}} +{"type":"system","subtype":"init","cwd":"/tmp/loop-bench-xnwdswui","session_id":"5d13072c-db37-4f2f-a05b-d00d1f108a49","tools":["Bash","Edit","Read"],"mcp_servers":[],"model":"glm-5.1","permissionMode":"dontAsk","slash_commands":["update-config","debug","simplify","batch","loop","schedule","claude-api","compact","context","cost","heapdump","init","review","security-review","insights"],"apiKeySource":"apiKeyHelper","claude_code_version":"2.1.92","output_style":"default","agents":["general-purpose","statusline-setup","Explore","Plan"],"skills":["update-config","debug","simplify","batch","loop","schedule","claude-api"],"plugins":[{"name":"claude-hud","path":"/root/.claude/plugins/marketplaces/claude-hud/","source":"claude-hud@claude-hud"},{"name":"rust-analyzer-lsp","path":"/root/.claude/plugins/marketplaces/claude-plugins-official/plugins/rust-analyzer-lsp","source":"rust-analyzer-lsp@claude-plugins-official"}],"uuid":"43e9fb0f-fd64-452a-a305-c6c2282c932f","fast_mode_state":"off"} +{"type":"assistant","message":{"id":"msg_2026040710110110189d5aadb047d8","type":"message","role":"assistant","model":"glm-5.1","content":[{"type":"thinking","thinking":"The user wants me to build a playable Tetris game in TypeScript that renders using WebGL and runs in a browser. Let me plan this out:\n\n1. I need to create an HTML file that loads the game\n2. I need TypeScript files for the game logic and WebGL rendering\n3. Standard Tetris mechanics: piece rotation, line clearing, scoring, increasing speed\n4. Keyboard controls for movement and rotation\n\nLet me think about the architecture:\n\n- `index.html` - Main HTML file\n- `src/game.ts` - Game logic (board, pieces, collision, line clearing, scoring)\n- `src/renderer.ts` - WebGL rendering\n- `src/main.ts` - Entry point, game loop, input handling\n- `tsconfig.json` - TypeScript config\n- `package.json` - For building\n\nActually, to keep it simpler and self-contained, let me create a single TypeScript file that compiles to a bundle, plus an HTML file. Or I could use a few files with a simple build setup.\n\nLet me think about what's most practical. I'll create:\n1. `index.html` - with canvas and UI\n2. `src/tetris.ts` - All game logic and WebGL rendering in one file (or split into a few)\n3. Build with `tsc` and a bundler, or just use esbuild\n\nLet me use esbuild for simplicity. I'll create:\n- `package.json`\n- `tsconfig.json`\n- `index.html`\n- `src/main.ts` - entry point with game loop and input\n- `src/board.ts` - game board logic\n- `src/pieces.ts` - tetromino definitions\n- `src/renderer.ts` - WebGL renderer\n- `src/shaders.ts` - GLSL shaders\n\nActually, let me keep it manageable. I'll create a few well-organized files.\n\nLet me think about the WebGL rendering approach:\n- Draw the board as a grid of colored quads\n- Each cell is a quad made of 2 triangles\n- Use instanced rendering or just batch all the vertices\n- Simple vertex + fragment shaders\n\nFor the game mechanics:\n- 10x20 board\n- 7 standard tetrominoes (I, O, T, S, Z, J, L)\n- SRS rotation system (or simplified)\n- Wall kicks\n- Line clearing with animation\n- Scoring: standard NES-style or guideline\n- Increasing speed (gravity increases with level)\n- Next piece preview\n- Ghost piece (shadow showing where piece will land)\n\nLet me design the WebGL renderer:\n- Orthographic projection\n- Draw filled rectangles for each cell\n- Use vertex buffer with all cell positions and colors\n- Update buffer each frame\n\nLet me start coding. I'll use esbuild for bundling.\n\nLet me create a clean, well-structured project:\n\n```\ntetris/\n package.json\n tsconfig.json\n index.html\n src/\n main.ts\n game.ts\n renderer.ts\n```\n\nActually, let me think about this more carefully. I want this to be a really nice Tetris game. Let me plan the WebGL rendering:\n\nThe board is 10 wide x 20 visible (+ 2-4 hidden rows at top).\n\nFor rendering, I'll:\n1. Use a single vertex buffer that I update each frame\n2. Each cell = 2 triangles = 6 vertices, each vertex has position (x,y) and color (r,g,b)\n3. Draw everything in one draw call\n\nFor the UI overlay (score, level, next piece), I can either:\n- Render them with WebGL too\n- Use HTML overlays on top of the canvas\n\nI'll use HTML for the UI elements (score, level, next piece display) and WebGL for the game board. This is cleaner.\n\nLet me think about the game state:\n- board: number[][] (10x24, where 0=empty, 1-7=colors)\n- currentPiece: { type, rotation, x, y }\n- nextPiece: type\n- score, level, lines\n- dropTimer, dropInterval\n- gameState: 'playing' | 'paused' | 'gameover'\n\nTetromino definitions - I'll store them as 4 rotations each, using a 4x4 or bounding box grid.\n\nStandard colors:\n- I: cyan\n- O: yellow\n- T: purple\n- S: green\n- Z: red\n- J: blue\n- L: orange\n\nLet me code this up now. I'll make it comprehensive and polished.\n\nFor the WebGL shaders:\n- Vertex shader: takes position and color attributes, passes color to fragment\n- Fragment shader: outputs the color, maybe with some slight shading for a 3D effect\n\nLet me also add:\n- Ghost piece (transparent version showing where piece will land)\n- Grid lines\n- Border around the playfield\n- Next piece preview\n- Hold piece\n- Hard drop (space bar)\n- Soft drop (down arrow)\n- Left/right movement\n- Rotation (up arrow or Z/X)\n\nControls:\n- Left/Right arrows: move piece\n- Down arrow: soft drop\n- Up arrow or X: rotate clockwise\n- Z: rotate counter-clockwise\n- Space: hard drop\n- P: pause\n\nScoring (NES-style):\n- 1 line: 40 * (level + 1)\n- 2 lines: 100 * (level + 1)\n- 3 lines: 300 * (level + 1)\n- 4 lines: 1200 * (level + 1)\n\nLevel increases every 10 lines.\nSpeed: dropInterval = max(100, 1000 - (level * 100)) or something like that.\n\nActually, let me use guideline scoring:\n- Single: 100 * level\n- Double: 300 * level\n- Triple: 500 * level\n- Tetris: 800 * level\n- Soft drop: 1 per cell\n- Hard drop: 2 per cell\n\nSpeed curve (guideline-like):\nLevel 0: 800ms, Level 1: 720ms, ... Level 9: 100ms, Level 10-12: 80ms, etc.\n\nLet me simplify: gravity = frames per gridcell. At 60fps:\nLevel 0: 48 frames (800ms)\nLevel 1: 43\n...\nLevel 9: 6\nLevel 10-12: 5\n...\n\nActually, let me just use a simple formula and not overcomplicate.\n\nOK let me start writing the code. I'll aim for clean, well-organized TypeScript.\n\nLet me structure the files:\n\n1. `index.html` - HTML with canvas and UI overlay\n2. `src/main.ts` - Entry point, initializes everything\n3. `src/game.ts` - Game logic class\n4. `src/renderer.ts` - WebGL renderer class\n5. `src/types.ts` - Type definitions\n6. `src/shaders.ts` - Shader source code\n\nActually, let me combine types into the files that use them. And shaders can be string constants in the renderer.\n\nLet me write the code:\n\n### types.ts\n```typescript\nexport type PieceType = 'I' | 'O' | 'T' | 'S' | 'Z' | 'J' | 'L';\n\nexport interface Piece {\n type: PieceType;\n rotation: number;\n x: number;\n y: number;\n}\n\nexport type GameState = 'playing' | 'paused' | 'gameover' | 'start';\n\nexport interface CellData {\n filled: boolean;\n color: [number, number, number];\n}\n```\n\n### game.ts\nThe game logic. Board is 10 columns x 24 rows (20 visible + 4 hidden). Pieces spawn at the top.\n\nI'll use SRS rotation data. Let me define the tetrominoes.\n\nStandard SRS piece data (using offsets from a reference point):\n\nI-piece rotations (in a 4x4 grid):\n```\nRotation 0: Rotation 1: Rotation 2: Rotation 3:\n. . . . . . X . . . . . . X . .\nX X X X . . X . . . . . . X . .\n. . . . . . X . X X X X . X . .\n. . . . . . X . . . . . . . . .\n```\n\nActually, let me just store the cells as coordinate arrays for each rotation. That's simpler.\n\nLet me define each piece as an array of 4 rotations, where each rotation is an array of 4 [row, col] coordinates.\n\nActually, the standard way to do this in Tetris implementations is to store the piece as a small grid and rotate it. Let me use coordinate offsets relative to the piece's position.\n\nHere are the SRS piece definitions:\n\n```\nI:\n0: [(0,0),(0,1),(0,2),(0,3)] -- horizontal at row 0\n1: [(0,0),(1,0),(2,0),(3,0)] -- wait, this isn't right for SRS\n```\n\nLet me just use the standard representation. I'll define each piece as a matrix and rotate it.\n\nActually, the simplest approach: define each piece as a 2D boolean array for rotation 0, then compute other rotations by rotation. But for accuracy, let me just hardcode all 4 rotations for each piece.\n\nLet me use the SRS definitions. The pieces are defined in a grid, and I'll store the cell coordinates for each rotation state.\n\nFor the I piece (4x4 bounding box):\nState 0: (1,0),(1,1),(1,2),(1,3)\nState 1: (0,2),(1,2),(2,2),(3,2)\nState 2: (2,0),(2,1),(2,2),(2,3)\nState 3: (0,1),(1,1),(2,1),(3,1)\n\nFor the O piece (3x3 bounding box, but it doesn't rotate):\nState 0-3: (0,1),(0,2),(1,1),(1,2)\n\nFor the T piece (3x3 bounding box):\nState 0: (0,1),(1,0),(1,1),(1,2)\nState 1: (0,1),(1,1),(1,2),(2,1)\nState 2: (1,0),(1,1),(1,2),(2,1)\nState 3: (0,1),(1,0),(1,1),(2,1)\n\nFor the S piece (3x3):\nState 0: (0,1),(0,2),(1,0),(1,1)\nState 1: (0,0),(1,0),(1,1),(2,1)\nState 2: (1,1),(1,2),(2,0),(2,1)\nState 3: (0,0),(1,0),(1,1),(2,1)\n\nHmm, I'm getting confused. Let me just define them as small grids and rotate programmatically.\n\nActually, the cleanest approach: define each piece as a list of (row, col) offsets for rotation 0, then compute rotations 1-3 by rotating 90° clockwise each time.\n\nRotation by 90° clockwise: (r, c) -> (c, -r) -> then normalize to have min row/col >= 0.\n\nWait, let me think again. If I have a piece in a bounding box of size N, rotation 90° CW maps (r, c) to (c, N-1-r).\n\nSo for a 3x3 box, (r, c) -> (c, 2-r)\nFor a 4x4 box, (r, c) -> (c, 3-r)\n\nLet me define the pieces for rotation 0 and compute the rest.\n\nI piece (4x4):\n```\n. . . .\nX X X X\n. . . .\n. . . .\n```\nCells: (1,0), (1,1), (1,2), (1,3)\n\nO piece (2x2 effectively, but I'll use 2x2):\n```\nX X\nX X\n```\nCells: (0,0), (0,1), (1,0), (1,1)\n\nT piece (3x3):\n```\n. X .\nX X X\n. . .\n```\nCells: (0,1), (1,0), (1,1), (1,2)\n\nS piece (3x3):\n```\n. X X\nX X .\n. . .\n```\nCells: (0,1), (0,2), (1,0), (1,1)\n\nZ piece (3x3):\n```\nX X .\n. X X\n. . .\n```\nCells: (0,0), (0,1), (1,1), (1,2)\n\nJ piece (3x3):\n```\nX . .\nX X X\n. . .\n```\nCells: (0,0), (1,0), (1,1), (1,2)\n\nL piece (3x3):\n```\n. . X\nX X X\n. . .\n```\nCells: (0,2), (1,0), (1,1), (1,2)\n\nOK, now for rotation. I'll compute rotation states at initialization.\n\nFor 3x3 pieces, CW rotation: (r, c) -> (c, 2-r)\nFor 4x4 pieces (I), CW rotation: (r, c) -> (c, 3-r)\nFor O piece: no change\n\nThis approach is clean. Let me implement it.\n\nFor SRS wall kicks, I'll implement the standard wall kick data. The wall kick tests for J, L, S, T, Z pieces use one table, and I piece uses another.\n\nWall kick data (offsets to try when rotation fails):\nFor JLSTZ pieces, when rotating from state R to new state:\nTest 1: (0, 0) - try without kick\nTest 2: depends on rotation transition\n...\n\nThe SRS wall kick data is:\n\nFrom 0->1: (0,0), (-1,0), (-1,+1), (0,-2), (-1,-2)\nFrom 1->0: (0,0), (+1,0), (+1,-1), (0,+2), (+1,+2)\nFrom 1->2: (0,0), (+1,0), (+1,-1), (0,+2), (+1,+2)\nFrom 2->1: (0,0), (-1,0), (-1,+1), (0,-2), (-1,-2)\nFrom 2->3: (0,0), (+1,0), (+1,+1), (0,-2), (+1,-2)\nFrom 3->2: (0,0), (-1,0), (-1,-1), (0,+2), (-1,+2)\nFrom 3->0: (0,0), (-1,0), (-1,-1), (0,+2), (-1,+2)\nFrom 0->3: (0,0), (+1,0), (+1,+1), (0,-2), (+1,-2)\n\nWait, I need to double-check the SRS wall kick data. The standard uses (x, y) where x is horizontal and y is vertical (positive = up). But in my coordinate system, rows increase downward. Let me be careful.\n\nActually, in the SRS specification, the wall kick data uses (column offset, row offset) where positive column is right and positive row is up. In my system, rows increase downward, so I need to negate the row component.\n\nLet me look up the exact SRS wall kick data:\n\nJLSTZ wall kick data:\n0->R: ( 0, 0), (-1, 0), (-1,+1), ( 0,-2), (-1,-2)\nR->2: ( 0, 0), (+1, 0), (+1,-1), ( 0,+2), (+1,+2)\n2->L: ( 0, 0), (+1, 0), (+1,+1), ( 0,-2), (+1,-2)\nL->0: ( 0, 0), (-1, 0), (-1,-1), ( 0,+2), (-1,+2)\n\nWhere the format is (x_offset, y_offset) with positive x = right, positive y = up.\n\nIn my coordinate system (col = x, row increases downward):\nx_offset stays the same, y_offset gets negated.\n\nSo:\n0->R: (0,0), (-1,0), (-1,-1), (0,+2), (-1,+2)\nR->2: (0,0), (+1,0), (+1,+1), (0,-2), (+1,-2)\n2->L: (0,0), (+1,0), (+1,-1), (0,+2), (+1,+2)\nL->0: (0,0), (-1,0), (-1,+1), (0,-2), (-1,-2)\n\nHmm wait, the SRS uses \"0, R, 2, L\" for the rotation states. 0 = spawn state, R = right (CW from 0), 2 = 180°, L = left (CCW from 0).\n\nAnd for the I piece:\n0->R: ( 0, 0), (-2, 0), (+1, 0), (-2,-1), (+1,+2)\nR->2: ( 0, 0), (-1, 0), (+2, 0), (-1,+2), (+2,-1)\n2->L: ( 0, 0), (+2, 0), (-1, 0), (+2,+1), (-1,-2)\nL->0: ( 0, 0), (+1, 0), (-2, 0), (+1,-2), (-2,+1)\n\nConverting y offsets to my coordinate system (negate):\n0->R: (0,0), (-2,0), (+1,0), (-2,+1), (+1,-2)\nR->2: (0,0), (-1,0), (+2,0), (-1,-2), (+2,+1)\n2->L: (0,0), (+2,0), (-1,0), (+2,-1), (-1,+2)\nL->0: (0,0), (+1,0), (-2,0), (+1,+2), (-2,-1)\n\nThis is getting complex. Let me simplify the implementation. I'll hardcode the wall kick data properly.\n\nOK let me just write the code. I'll be thorough but practical.\n\nLet me think about the WebGL rendering more carefully.\n\nThe canvas will show:\n- The playfield (10x20 grid of cells)\n- Each cell can be empty (dark background) or filled with a color\n- Grid lines separating cells\n- A border around the playfield\n- Ghost piece (semi-transparent)\n- Current piece\n\nFor the WebGL approach:\n1. Set up an orthographic projection that maps to pixel coordinates (or normalized coordinates)\n2. Each frame, build a vertex array for all the quads to draw\n3. Each vertex has: position (x, y), color (r, g, b, a)\n4. Draw everything with gl.TRIANGLES\n\nI'll also add some visual polish:\n- Slight gradient/shading on blocks to give a 3D effect\n- Flashing effect when lines are cleared\n- Smooth animations\n\nActually, for a clean implementation, let me:\n1. Use a coordinate system where the playfield is centered in the canvas\n2. Each cell is a unit square\n3. Render grid lines as thin quads\n4. Render filled cells as quads with the piece color\n5. Render the ghost piece with alpha\n\nFor the vertex format, I'll use interleaved data: x, y, r, g, b, a per vertex.\n\nThe shaders:\n```glsl\n// Vertex shader\nattribute vec2 aPosition;\nattribute vec4 aColor;\nuniform vec2 uResolution;\nvarying vec4 vColor;\n\nvoid main() {\n vec2 clipSpace = (aPosition / uResolution) * 2.0 - 1.0;\n gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);\n vColor = aColor;\n}\n\n// Fragment shader\nprecision mediump float;\nvarying vec4 vColor;\nvoid main() {\n gl_FragColor = vColor;\n}\n```\n\nWait, but I want to add some visual effects to the blocks. Maybe a slight border/bevel effect. I could pass UV coordinates within each cell and use the fragment shader to create the bevel effect.\n\nLet me enhance: each vertex has x, y, u, v, r, g, b, a\nWhere u, v are 0-1 within the cell (0,0 = top-left, 1,1 = bottom-right)\n\nThen in the fragment shader, I can create a bevel effect:\n```glsl\nfloat border = 0.1;\nfloat bevel = 0.15;\nvec4 color = vColor;\nif (u < border || v < border || u > 1.0-border || v > 1.0-border) {\n color *= 0.5; // dark border\n} else if (u < border+bevel && v < border+bevel) {\n // top-left highlight (already default brightness)\n} else if (u > 1.0-border-bevel && v > 1.0-border-bevel) {\n color *= 0.7; // bottom-right shadow\n}\n```\n\nThis would give a nice 3D block look. Let me implement this.\n\nActually, let me simplify the bevel effect. I'll use a cleaner approach:\n\n```glsl\n// Inside the cell (u,v from 0 to 1)\nfloat edge = 0.08;\nfloat bevel = 0.15;\nvec3 col = vColor.rgb;\n\n// Dark border around edges\nfloat borderFactor = smoothstep(0.0, edge, u) * smoothstep(1.0, 1.0-edge, u) *\n smoothstep(0.0, edge, v) * smoothstep(1.0, 1.0-edge, v);\n\n// Highlight on top-left bevel\nfloat highlight = smoothstep(edge+bevel, edge, u) * borderFactor +\n smoothstep(edge+bevel, edge, v) * borderFactor;\n\n// Shadow on bottom-right\nfloat shadow = smoothstep(1.0-edge-bevel, 1.0-edge, u) * borderFactor +\n smoothstep(1.0-edge-bevel, 1.0-edge, v) * borderFactor;\n\ncol = col * (0.6 + 0.4 * borderFactor) + vec3(0.2) * highlight - vec3(0.15) * shadow;\n```\n\nHmm, this is getting complicated. Let me keep it simple but visually appealing. I'll add a basic bevel effect.\n\nLet me now plan the full implementation and start coding.\n\nFiles I'll create:\n1. `package.json` - with esbuild\n2. `tsconfig.json`\n3. `index.html`\n4. `src/main.ts` - Entry point\n5. `src/game.ts` - Game logic\n6. `src/renderer.ts` - WebGL renderer\n\nLet me write them all out. This will be substantial code but I want it to be good.\n\nLet me think about the structure of renderer.ts more carefully.\n\nThe renderer needs to:\n1. Initialize WebGL context\n2. Compile shaders\n3. Set up buffers\n4. Each frame:\n a. Clear the canvas\n b. Build vertex data for all quads (board cells, current piece, ghost piece, grid, border)\n c. Upload vertex data to GPU\n d. Draw\n\nFor the vertex data, I'll pre-allocate a Float32Array that's large enough.\n\nEach vertex: x, y, u, v, r, g, b, a = 8 floats\nEach quad: 6 vertices (2 triangles) = 48 floats\nMax quads: let's say 300 (board is 200 cells + pieces + grid + border) -> 14400 floats\n\nThat's very manageable.\n\nLet me write the code now. I'll be comprehensive.\n\nActually, let me reconsider the project structure. Since I want this to be easy to build and run, I'll use a simple setup:\n\n```json\n{\n \"scripts\": {\n \"build\": \"esbuild src/main.ts --bundle --outfile=public/bundle.js\",\n \"dev\": \"esbuild src/main.ts --bundle --outfile=public/bundle.js --watch --servedir=public\"\n }\n}\n```\n\nWait, but index.html should reference the bundle. Let me put index.html in public/.\n\nActually, let me just put everything flat:\n- `index.html` at root\n- `src/main.ts`, `src/game.ts`, `src/renderer.ts`\n- Build outputs `bundle.js` at root\n\nOK let me just start writing the code.\n\nLet me think about the canvas sizing. I want the playfield to be nicely sized. The playfield is 10x20. If each cell is 30px, that's 300x600. Plus side panels for score and next piece. Total canvas: maybe 500x640 or so. I'll make it responsive but start with a fixed size.\n\nActually, let me make the canvas fill more of the screen and have the game centered. The playfield will be in the center with score/next piece on the right side, all rendered in WebGL.\n\nHmm, rendering text in WebGL is complex. Let me use HTML overlays for text (score, level, next piece info) and use the canvas only for the game board. This is much cleaner.\n\nLet me design the HTML:\n```html\n<div id=\"game-container\">\n <div id=\"left-panel\">\n <div>HOLD</div>\n <canvas id=\"hold-canvas\"></canvas>\n </div>\n <canvas id=\"game-canvas\"></canvas>\n <div id=\"right-panel\">\n <div>NEXT</div>\n <canvas id=\"next-canvas\"></canvas>\n <div>SCORE: <span id=\"score\">0</span></div>\n <div>LEVEL: <span id=\"level\">0</span></div>\n <div>LINES: <span id=\"lines\">0</span></div>\n </div>\n</div>\n```\n\nActually, let me simplify. I'll have one main canvas for the playfield, and use HTML for everything else. The next piece and hold piece can be small canvases or I can draw them in the main canvas.\n\nLet me go with a single canvas approach for simplicity. The canvas will have the playfield on the left portion and info (next piece, score, etc.) drawn with WebGL too. For text, I'll overlay HTML elements.\n\nOr... let me just use a simple approach: one canvas for the game, HTML for the UI. The canvas handles the playfield, and small canvases for next/hold pieces.\n\nActually, the simplest approach that looks good: single large canvas that renders everything including the playfield, side panels with colored rectangles for pieces, etc. For text, use HTML overlays positioned on top.\n\nLet me go with: HTML for text UI, canvas for all the graphical game elements. I'll position HTML elements absolutely over the canvas.\n\nOK I think I'm overcomplicating this. Let me just write the code.\n\nFinal plan:\n- Single canvas, sized to fit the game\n- HTML elements for score, level, lines, next piece label\n- WebGL renders: playfield grid, filled cells, current piece, ghost piece, next piece preview, border\n- The canvas will be sized so the playfield is on the left and there's space on the right for next piece\n\nCanvas layout:\n- Playfield: 10 cells wide, 20 cells tall, starting at some offset\n- Cell size: 30px\n- Playfield: 300x600\n- Next piece area: to the right of the playfield\n- Total canvas: ~440x660 or so\n\nActually let me make it adaptive. Let me calculate the cell size based on the window.\n\nOK let me just start writing. I'll make it work first, then we can polish.\n\nLet me define the cell size as a constant and build from there.\n\nLet me write all the files now.\n\n### package.json\n```json\n{\n \"name\": \"tetris-webgl\",\n \"version\": \"1.0.0\",\n \"private\": true,\n \"scripts\": {\n \"build\": \"esbuild src/main.ts --bundle --outfile=dist/bundle.js --minify\",\n \"dev\": \"esbuild src/main.ts --bundle --outfile=dist/bundle.js --watch --servedir=.\"\n },\n \"devDependencies\": {\n \"esbuild\": \"^0.20.0\",\n \"typescript\": \"^5.3.0\"\n }\n}\n```\n\nWait, I don't need `dist/`. Let me just output to `bundle.js` and reference it from `index.html`.\n\nActually, for dev server to work properly with esbuild's `--servedir`, let me structure it as:\n- `index.html` at root references `bundle.js`\n- Build outputs `bundle.js` at root\n\n```json\n{\n \"scripts\": {\n \"build\": \"esbuild src/main.ts --bundle --outfile=bundle.js\",\n \"dev\": \"esbuild src/main.ts --bundle --outfile=bundle.js --watch --servedir=.\"\n }\n}\n```\n\n### tsconfig.json\n```json\n{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"module\": \"ESNext\",\n \"strict\": true,\n \"outDir\": \"./dist\",\n \"rootDir\": \"./src\",\n \"esModuleInterop\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"skipLibCheck\": true\n },\n \"include\": [\"src/**/*\"]\n}\n```\n\n### index.html\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Tetris WebGL</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n background: #1a1a2e;\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n font-family: 'Segoe UI', monospace;\n color: #eee;\n overflow: hidden;\n }\n #game-wrapper {\n display: flex;\n gap: 20px;\n align-items: flex-start;\n }\n #canvas-container {\n position: relative;\n }\n canvas {\n display: block;\n border: 2px solid #444;\n border-radius: 4px;\n }\n .side-panel {\n width: 160px;\n display: flex;\n flex-direction: column;\n gap: 15px;\n }\n .panel-box {\n background: rgba(255,255,255,0.05);\n border: 1px solid #333;\n border-radius: 6px;\n padding: 12px;\n }\n .panel-box h3 {\n font-size: 12px;\n color: #888;\n text-transform: uppercase;\n letter-spacing: 2px;\n margin-bottom: 8px;\n }\n .panel-box .value {\n font-size: 24px;\n font-weight: bold;\n }\n canvas.preview {\n width: 120px;\n height: 120px;\n }\n #controls {\n font-size: 11px;\n color: #666;\n line-height: 1.8;\n }\n #start-screen, #gameover-screen {\n position: absolute;\n top: 0; left: 0; right: 0; bottom: 0;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n background: rgba(0,0,0,0.8);\n z-index: 10;\n }\n #start-screen h1, #gameover-screen h1 {\n font-size: 48px;\n margin-bottom: 20px;\n }\n .hidden { display: none !important; }\n button {\n padding: 12px 30px;\n font-size: 18px;\n background: #e94560;\n color: white;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n }\n button:hover {\n background: #ff6b81;\n }\n </style>\n</head>\n<body>\n <div id=\"game-wrapper\">\n <div id=\"canvas-container\">\n <canvas id=\"game-canvas\"></canvas>\n <div id=\"start-screen\">\n <h1>TETRIS</h1>\n <button id=\"start-btn\">START GAME</button>\n </div>\n <div id=\"gameover-screen\" class=\"hidden\">\n <h1>GAME OVER</h1>\n <p id=\"final-score\"></p>\n <button id=\"restart-btn\">PLAY AGAIN</button>\n </div>\n </div>\n <div class=\"side-panel\">\n <div class=\"panel-box\">\n <h3>Next</h3>\n <canvas id=\"next-canvas\" class=\"preview\" width=\"120\" height=\"120\"></canvas>\n </div>\n <div class=\"panel-box\">\n <h3>Score</h3>\n <div class=\"value\" id=\"score\">0</div>\n </div>\n <div class=\"panel-box\">\n <h3>Level</h3>\n <div class=\"value\" id=\"level\">1</div>\n </div>\n <div class=\"panel-box\">\n <h3>Lines</h3>\n <div class=\"value\" id=\"lines\">0</div>\n </div>\n <div class=\"panel-box\" id=\"controls\">\n <h3>Controls</h3>\n ← → Move<br>\n ↓ Soft Drop<br>\n ↑ Rotate CW<br>\n Z Rotate CCW<br>\n Space Hard Drop<br>\n P Pause\n </div>\n </div>\n </div>\n <script src=\"bundle.js\"></script>\n</body>\n</html>\n```\n\nNow for the TypeScript code. Let me write the game logic first.\n\n### src/game.ts\n\n```typescript\nexport type PieceType = 0 | 1 | 2 | 3 | 4 | 5 | 6;\n// 0=I, 1=O, 2=T, 3=S, 4=Z, 5=J, 6=L\n\nexport const PIECE_TYPES = 7;\n\nconst BOARD_WIDTH = 10;\nconst BOARD_HEIGHT = 24; // 4 hidden rows at top\nconst VISIBLE_HEIGHT = 20;\n\n// Piece colors as RGB (0-1)\nexport const PIECE_COLORS: [number, number, number][] = [\n [0, 0.9, 0.9], // I - cyan\n [0.9, 0.9, 0], // O - yellow\n [0.7, 0, 0.9], // T - purple\n [0, 0.9, 0], // S - green\n [0.9, 0, 0], // Z - red\n [0, 0, 0.9], // J - blue\n [0.9, 0.5, 0], // L - orange\n];\n\n// Piece shapes - each rotation is an array of [row, col] offsets\n// Defined for rotation state 0, others computed\n\nconst PIECE_SHAPES: number[][][] = [\n // I piece\n [\n [0, 0], [0, 1], [0, 2], [0, 3]\n ],\n // O piece\n [\n [0, 0], [0, 1], [1, 0], [1, 1]\n ],\n // T piece\n [\n [0, 1], [1, 0], [1, 1], [1, 2]\n ],\n // S piece\n [\n [0, 1], [0, 2], [1, 0], [1, 1]\n ],\n // Z piece\n [\n [0, 0], [0, 1], [1, 1], [1, 2]\n ],\n // J piece\n [\n [0, 0], [1, 0], [1, ","signature":""}],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":0,"output_tokens":0},"context_management":null},"parent_tool_use_id":null,"session_id":"5d13072c-db37-4f2f-a05b-d00d1f108a49","uuid":"51b14e37-2adb-45f5-9623-5ccdcf3b4588"}