diff --git a/agent-test/docs/asset_protocol.md b/agent-test/docs/asset_protocol.md index 9eac1d4d4..5417ac27f 100644 --- a/agent-test/docs/asset_protocol.md +++ b/agent-test/docs/asset_protocol.md @@ -37,6 +37,30 @@ If generating \*too many assets**, split into **2 separate tool calls\*\* to avo | `image` | `key`, `description` | PNG 386\*560 (portrait) | Yes | | `audio` | `key`, `description`, `audioType`, `duration?`, `genre?`, `tempo?` | WAV (8-bit chiptune) | N/A | +### 1.2.0 3D Image Assets (`threed_basic`) + +Three.js games use the same `generate_game_assets` image pipeline. These are +2D images applied to geometry; they are not 3D models and do not create a new +tool type. + +| 3D role | Existing type | Required description/runtime contract | +|---|---|---| +| skybox | `background` | equirectangular sky texture, `1024*1024`, no background removal | +| surface texture | `image` | seamless or centered surface art, runtime display/source <= `1024*1024` | +| billboard sprite | `image` | one centered subject, transparent background/removal allowed | +| circular floor patch | `image` | top-down circular patch, transparent outside edge | + +- Asset Registry `displaySize` remains GDD/runtime metadata; never pass it as + an unsupported MCP parameter. Source textures and declared display size must + not exceed `1024*1024`. +- Keep every 3D image in the normal Asset Registry/display list and generated + `asset-pack.json`; key and URL consistency rules below are unchanged. +- `colormap` is allowed only for matte/non-glossy art. Never colormap glossy, + translucent, emissive, metallic, or skybox art. +- Allowed runtime consumers are `Texture`, `MeshStandardMaterial.map`, scene + background, and `SpriteMaterial`. Never request GLB/FBX/OBJ, normal maps, + model generation, or a text-to-3D API. + **CRITICAL — Parameter restrictions:** - `type: "image"` accepts ONLY `key` and `description`. **Do NOT pass `size`, `resolution`, or any other parameter** — the output is always 386\*560 PNG. Game code scales the image via `setScale()` or `setDisplaySize()`. Icons, projectiles, and small sprites all use the same output size; scale in code. diff --git a/agent-test/docs/modules/threed_basic/design_rules.md b/agent-test/docs/modules/threed_basic/design_rules.md new file mode 100644 index 000000000..d8cc4e95d --- /dev/null +++ b/agent-test/docs/modules/threed_basic/design_rules.md @@ -0,0 +1,50 @@ +# threed_basic Design Rules + +## 1. Product shape + +Build one short, finishable 3D route: the player moves through a single world, +collects every required item, and reaches one completion state. Use three.js, +simple geometry, lights, fog, a sky texture, and DOM overlays. + +Do not add physics, touch controls, multiplayer, imported 3D models, or any +text-to-3D service. 2D Phaser templates are unrelated and must remain unchanged. + +## 2. Required runtime contract + +| Area | Required | +|---|---| +| Renderer | `WebGLRenderer`, `PerspectiveCamera`, resize handling, visible non-black frame | +| World | primitives or custom low-poly geometry, ambient + directional light, fog | +| Input | WASD and arrow keys; mouse drag/look; ESC pause | +| HUD | DOM in `#ui-root`; canvas stays dedicated to three.js | +| Pause | resolve `gameSceneKey ?? currentLevelKey ?? LevelManager.getFirstLevelScene()` | +| Win | one explicit, reachable completion condition | + +## 3. Asset registry rules + +3D assets are still ordinary images produced by `generate_game_assets`. + +| Key role | Tool type | Max/source rule | three.js use | +|---|---|---|---| +| `skybox_texture` | `background` | `1024*1024`; `displaySize: 1024*1024` in GDD | equirectangular scene background | +| `floor_patch` | `image` | generated image; declare display size <= `1024*1024` | `CircleGeometry` material map | +| `energy_billboard` | `image` | one centered subject, transparent removal allowed | `SpriteMaterial` | +| surface texture | `image` | <= `1024*1024`; no glossy colormap | `MeshStandardMaterial.map` | + +Never request a model, mesh, GLB, FBX, normal map, or text-to-3D output. Do not +use `colormap` for glossy, translucent, emissive, or sky assets. + +## 4. Level and camera budget + +Use 8-14 floor patches, 5-10 collectibles, and 8-16 low-poly decorations. +Keep the camera far plane under 250 and cap device pixel ratio at 2. Manual +distance checks are enough for pickups; do not introduce a physics dependency. + +## 5. GDD completion notes + +The GDD must end with: + +- the actual generated keys and their runtime consumers; +- any placeholder/fallback used; +- `3D scope: primitives + generated image textures; no model generation`; +- the command evidence from build and smoke. diff --git a/agent-test/docs/modules/threed_basic/template_api.md b/agent-test/docs/modules/threed_basic/template_api.md new file mode 100644 index 000000000..2ba793f83 --- /dev/null +++ b/agent-test/docs/modules/threed_basic/template_api.md @@ -0,0 +1,65 @@ +# threed_basic Template API + +## Scaffolded files + +| File | Contract | +|---|---| +| `src/main.ts` | boots title, runtime, DOM HUD, pause, completion, render loop | +| `src/GameScene.ts` | owns renderer, scene, camera, world, manual pickup checks | +| `src/InputController.ts` | keyboard + mouse state only | +| `src/SceneMap.ts` | Editor-facing declarative positions via `initSceneMap()` | +| `src/ThreeSceneDefaults.ts` | shared light, fog, and background defaults | +| `src/gameConfig.json` | wrapped `{ value, type, description }` tuning fields | + +## `GameScene` constructor + +```ts +new GameScene(container, { + onProgress(collected, total) {}, + onComplete() {}, + onGameOver() {}, +}, preloader.textures); +``` + +Required public methods: + +| Method | Meaning | +|---|---| +| `update(deltaSeconds)` | advance input, collection, animation, and render | +| `setPaused(boolean)` | stop/resume simulation and clear held input | +| `isPaused()` | smoke/lifecycle observable pause state | +| `resize()` | update camera aspect and renderer size | + +## SceneMap + +`initSceneMap()` returns `floorPatches`, `collectibles`, and `obstacles`. +Change positions there instead of hard-coding level coordinates inside the +render loop. Add new declarative arrays at the `// EXT` point only when a real +consumer is implemented. + +## Texture keys + +`Preloader` reads Phaser-compatible `asset-pack.json` sections and loads image +entries into `Map`. The reference module recognizes: + +| Key | Fallback | +|---|---| +| `skybox_texture` | dark blue `Color` background | +| `floor_patch` | rough blue material | +| `energy_billboard` | emissive sphere primitive | + +Missing optional textures must not throw or block the game. + +## Pause data contract + +Both `UIScene.init()` and `PauseUIScene.init()` accept: + +```ts +{ gameSceneKey?: string; currentLevelKey?: string } +``` + +Resolve in this exact order: + +```ts +data.gameSceneKey ?? data.currentLevelKey ?? LevelManager.getFirstLevelScene() +``` diff --git a/agent-test/docs/modules/threed_basic/threed_basic.md b/agent-test/docs/modules/threed_basic/threed_basic.md new file mode 100644 index 000000000..0826130eb --- /dev/null +++ b/agent-test/docs/modules/threed_basic/threed_basic.md @@ -0,0 +1,56 @@ +# threed_basic Implementation Manual + +Read this file and every scaffolded `src/` file before editing the generated +game. Keep the reference lifecycle intact; customize data, materials, text, +and the one level rather than replacing the shell. + +## Phase 5 implementation order + +| Order | Action | Done when | +|---|---|---| +| 1 | map GDD asset keys to `skybox_texture`, `floor_patch`, `energy_billboard` | every used key exists in `asset-pack.json` | +| 2 | edit `SceneMap.ts` | main route and every pickup are reachable | +| 3 | merge tuning into `gameConfig.json` | wrapper shape and core fields remain | +| 4 | theme materials and DOM text | canvas remains WebGL-only; HUD remains DOM-only | +| 5 | run build and smoke | zero errors, non-black canvas, WebGL context, ESC resume | + +## Runtime lifecycle + +```text +Preloader.load -> TitleScreen.show -> GameScene constructor + -> applyThreeSceneDefaults -> initSceneMap -> HUD.show + -> requestAnimationFrame -> GameScene.update -> renderer.render + -> all collectibles removed -> onComplete -> GameCompleteUIScene +``` + +`deltaSeconds` is capped by `main.ts`; all movement must multiply by it. +`setPaused(true)` must clear input so a key held before pause cannot continue +moving after resume. + +## Asset hookup + +Only call `generate_game_assets`. A skybox is a generated 2D equirectangular +image, floor art is a generated 2D patch/texture, and an energy marker is a +generated billboard sprite. Do not call shell image tools or any 3D model API. + +If a texture is unavailable, keep the supplied primitive fallback and append +the fallback key to the GDD Asset Degradation Log. The fallback makes the game +playable; it does not authorize skipping the required asset call. + +## Manual play check + +1. Press Enter on the title screen. +2. Move with W/A/S/D or arrow keys; drag the mouse to look. +3. Press ESC, confirm the pause overlay, then ESC again and confirm movement. +4. Follow the single route and collect all energy markers. +5. Confirm `TRAIL COMPLETE`, then verify restart. + +## Frequent failures + +| Symptom | Root cause | Fix | +|---|---|---| +| black canvas, no console error | level never rendered after title | keep the RAF loop and call `renderer.render` every active frame | +| ESC overlay opens but game stays paused | wrong scene key | use the three-key fallback contract exactly | +| image 404s | invented key or leading slash mismatch | read the generated `asset-pack.json`; use its key/url | +| movement depends on frame rate | raw per-frame displacement | multiply by capped `deltaSeconds` | +| huge GPU cost | uncapped DPR or oversized textures | DPR <= 2; texture/display size <= 1024 squared | diff --git a/agent-test/templates/core3d/.gitignore b/agent-test/templates/core3d/.gitignore new file mode 100644 index 000000000..8a55dd1f1 --- /dev/null +++ b/agent-test/templates/core3d/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +shots diff --git a/agent-test/templates/core3d/README.md b/agent-test/templates/core3d/README.md new file mode 100644 index 000000000..a1e970e34 --- /dev/null +++ b/agent-test/templates/core3d/README.md @@ -0,0 +1,10 @@ +# OpenGame core3d template + +Minimal three.js + TypeScript + Vite shell. It preserves the existing DOM +screen contract while keeping 3D rendering isolated from every Phaser template. + +```bash +npm ci +npm run build +npm run dev +``` diff --git a/agent-test/templates/core3d/index.html b/agent-test/templates/core3d/index.html new file mode 100644 index 000000000..432b6fdbf --- /dev/null +++ b/agent-test/templates/core3d/index.html @@ -0,0 +1,13 @@ + + + + + + OpenGame 3D + + +
+
+ + + diff --git a/agent-test/templates/core3d/package-lock.json b/agent-test/templates/core3d/package-lock.json new file mode 100644 index 000000000..0858ef515 --- /dev/null +++ b/agent-test/templates/core3d/package-lock.json @@ -0,0 +1,2361 @@ +{ + "name": "opengame-core3d-template", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opengame-core3d-template", + "version": "0.0.0", + "dependencies": { + "three": "0.179.1" + }, + "devDependencies": { + "@types/three": "0.179.0", + "autoprefixer": "10.4.21", + "postcss": "8.5.25", + "tailwindcss": "3.4.18", + "typescript": "5.8.3", + "vite": "6.4.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.179.0", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.179.0.tgz", + "integrity": "sha512-VgbFG2Pgsm84BqdegZzr7w2aKbQxmgzIu4Dy7/75ygiD/0P68LKmp5ie08KMPNqGTQwIge8s6D1guZf1RnZE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": "*", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~0.22.0" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webgpu/types": { + "version": "0.1.71", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", + "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.12", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", + "integrity": "sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/browserslist": { + "version": "4.28.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", + "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.44", + "caniuse-lite": "^1.0.30001806", + "electron-to-chromium": "^1.5.393", + "node-releases": "^2.0.51", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/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/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.400", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.400.tgz", + "integrity": "sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==", + "dev": true, + "license": "ISC" + }, + "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/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/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/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "dev": true, + "license": "MIT" + }, + "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/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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/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/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-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/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "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/meshoptimizer": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.22.0.tgz", + "integrity": "sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==", + "dev": true, + "license": "MIT" + }, + "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/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.52", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.52.tgz", + "integrity": "sha512-MRlTqhAfoMx/4mhEbPo3Hi02g9LJZaJkka69V6h67Cb1gjrAG0jsTE4CZX1eptNx+VCAwJmfpnDIF4P0Nh1A7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "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/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "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/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "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/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "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/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/three": { + "version": "0.179.1", + "resolved": "https://registry.npmjs.org/three/-/three-0.179.1.tgz", + "integrity": "sha512-5y/elSIQbrvKOISxpwXCR4sQqHtGiOI+MKLc3SsBdDXA2hz3Mdp3X59aUp8DyybMa34aeBwbFTpdoLJaUDEWSw==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + } + } +} diff --git a/agent-test/templates/core3d/package.json b/agent-test/templates/core3d/package.json new file mode 100644 index 000000000..7fbaf2b08 --- /dev/null +++ b/agent-test/templates/core3d/package.json @@ -0,0 +1,23 @@ +{ + "name": "opengame-core3d-template", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "typecheck": "tsc --noEmit", + "build": "tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "three": "0.179.1" + }, + "devDependencies": { + "@types/three": "0.179.0", + "autoprefixer": "10.4.21", + "postcss": "8.5.25", + "tailwindcss": "3.4.18", + "typescript": "5.8.3", + "vite": "6.4.3" + } +} diff --git a/agent-test/templates/core3d/postcss.config.js b/agent-test/templates/core3d/postcss.config.js new file mode 100644 index 000000000..e008c9cee --- /dev/null +++ b/agent-test/templates/core3d/postcss.config.js @@ -0,0 +1,3 @@ +export default { + plugins: { tailwindcss: {}, autoprefixer: {} }, +}; diff --git a/agent-test/templates/core3d/src/GameScene.ts b/agent-test/templates/core3d/src/GameScene.ts new file mode 100644 index 000000000..120000b7a --- /dev/null +++ b/agent-test/templates/core3d/src/GameScene.ts @@ -0,0 +1,144 @@ +import { + AmbientLight, + BoxGeometry, + Color, + DirectionalLight, + Fog, + Mesh, + MeshStandardMaterial, + PerspectiveCamera, + PlaneGeometry, + Scene, + Texture, + WebGLRenderer, +} from 'three'; +import gameConfig from './gameConfig.json'; + +export type GameSceneEvents = { + onProgress: (collected: number, total: number) => void; + onComplete: () => void; + onGameOver: () => void; +}; + +export class GameScene { + readonly renderer: WebGLRenderer; + private readonly scene = new Scene(); + private readonly camera = new PerspectiveCamera(65, 1, 0.1, 200); + private readonly keys = new Set(); + private paused = false; + private yaw = 0; + private dragging = false; + + constructor( + private readonly container: HTMLElement, + private readonly events: GameSceneEvents, + protected readonly textures: Map = new Map(), + ) { + this.renderer = new WebGLRenderer({ antialias: true, preserveDrawingBuffer: true }); + this.renderer.setPixelRatio( + Math.min(devicePixelRatio, gameConfig.renderConfig.pixelRatioCap.value), + ); + this.renderer.setSize(container.clientWidth, container.clientHeight); + container.replaceChildren(this.renderer.domElement); + this.buildWorld(); + this.bindInputs(); + this.resize(); + this.events.onProgress(0, 0); + } + + private buildWorld(): void { + this.scene.background = new Color(0x071426); + this.scene.fog = new Fog( + 0x071426, + gameConfig.renderConfig.fogNear.value, + gameConfig.renderConfig.fogFar.value, + ); + this.scene.add(new AmbientLight(0x9ccfff, 1.5)); + const sun = new DirectionalLight(0xffffff, 3); + sun.position.set(8, 14, 6); + this.scene.add(sun); + + const floor = new Mesh( + new PlaneGeometry(18, 90), + new MeshStandardMaterial({ color: 0x123765, roughness: 0.8 }), + ); + floor.rotation.x = -Math.PI / 2; + floor.position.z = -32; + this.scene.add(floor); + + const geometry = new BoxGeometry(1.5, 1.5, 1.5); + for (let i = 0; i < 14; i++) { + const marker = new Mesh( + geometry, + new MeshStandardMaterial({ color: i % 2 ? 0x22d3ee : 0xa855f7 }), + ); + marker.position.set(i % 2 ? 5 : -5, 1, -i * 6); + this.scene.add(marker); + } + this.camera.position.set(0, 2.2, 7); + } + + private bindInputs(): void { + window.addEventListener('keydown', this.onKeyDown); + window.addEventListener('keyup', this.onKeyUp); + this.renderer.domElement.addEventListener('pointerdown', this.onPointerDown); + window.addEventListener('pointerup', this.onPointerUp); + window.addEventListener('pointermove', this.onPointerMove); + } + + private readonly onKeyDown = (event: KeyboardEvent): void => { + this.keys.add(event.code); + }; + + private readonly onKeyUp = (event: KeyboardEvent): void => { + this.keys.delete(event.code); + }; + + private readonly onPointerDown = (): void => { + this.dragging = true; + }; + + private readonly onPointerUp = (): void => { + this.dragging = false; + }; + + private readonly onPointerMove = (event: PointerEvent): void => { + if (this.dragging) this.yaw -= event.movementX * gameConfig.playerConfig.mouseSensitivity.value; + }; + + update(deltaSeconds: number): void { + if (this.paused) return; + const forward = + Number(this.keys.has('KeyW') || this.keys.has('ArrowUp')) - + Number(this.keys.has('KeyS') || this.keys.has('ArrowDown')); + const strafe = + Number(this.keys.has('KeyD') || this.keys.has('ArrowRight')) - + Number(this.keys.has('KeyA') || this.keys.has('ArrowLeft')); + const speed = gameConfig.playerConfig.moveSpeed.value * deltaSeconds; + this.camera.position.x += + (Math.cos(this.yaw) * strafe + Math.sin(this.yaw) * forward) * speed; + this.camera.position.z += + (Math.sin(this.yaw) * strafe - Math.cos(this.yaw) * forward) * speed; + this.camera.position.x = Math.max(-8, Math.min(8, this.camera.position.x)); + this.camera.position.z = Math.max(-78, Math.min(8, this.camera.position.z)); + this.camera.rotation.y = this.yaw; + this.renderer.render(this.scene, this.camera); + } + + setPaused(paused: boolean): void { + this.paused = paused; + this.keys.clear(); + } + + isPaused(): boolean { + return this.paused; + } + + resize(): void { + const width = Math.max(1, this.container.clientWidth); + const height = Math.max(1, this.container.clientHeight); + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(width, height); + } +} diff --git a/agent-test/templates/core3d/src/LevelManager.ts b/agent-test/templates/core3d/src/LevelManager.ts new file mode 100644 index 000000000..e35bb131c --- /dev/null +++ b/agent-test/templates/core3d/src/LevelManager.ts @@ -0,0 +1,7 @@ +export class LevelManager { + static readonly LEVEL_ORDER = ['GameScene']; + + static getFirstLevelScene(): string { + return LevelManager.LEVEL_ORDER[0]; + } +} diff --git a/agent-test/templates/core3d/src/gameConfig.json b/agent-test/templates/core3d/src/gameConfig.json new file mode 100644 index 000000000..f23a8fad7 --- /dev/null +++ b/agent-test/templates/core3d/src/gameConfig.json @@ -0,0 +1,15 @@ +{ + "screenSize": { + "width": { "value": 1152, "type": "number", "description": "Reference width" }, + "height": { "value": 768, "type": "number", "description": "Reference height" } + }, + "renderConfig": { + "pixelRatioCap": { "value": 2, "type": "number", "description": "Renderer pixel ratio cap" }, + "fogNear": { "value": 18, "type": "number", "description": "Fog near distance" }, + "fogFar": { "value": 85, "type": "number", "description": "Fog far distance" } + }, + "playerConfig": { + "moveSpeed": { "value": 8, "type": "number", "description": "Movement units per second" }, + "mouseSensitivity": { "value": 0.002, "type": "number", "description": "Mouse look sensitivity" } + } +} diff --git a/agent-test/templates/core3d/src/main.ts b/agent-test/templates/core3d/src/main.ts new file mode 100644 index 000000000..2067f95be --- /dev/null +++ b/agent-test/templates/core3d/src/main.ts @@ -0,0 +1,88 @@ +import './styles/tailwind.css'; +import { GameScene } from './GameScene'; +import { Preloader } from './scenes/Preloader'; +import { TitleScreen } from './scenes/TitleScreen'; +import { UIScene } from './scenes/UIScene'; +import { PauseUIScene } from './scenes/PauseUIScene'; +import { GameCompleteUIScene } from './scenes/GameCompleteUIScene'; +import { GameOverUIScene } from './scenes/GameOverUIScene'; + +declare global { + interface Window { + __opengame3d?: { isPaused: () => boolean; renderer: string }; + } +} + +class GameApp { + private readonly container = document.getElementById('game-container'); + private readonly preloader = new Preloader(); + private readonly title = new TitleScreen(); + private readonly ui = new UIScene(); + private readonly pauseUi = new PauseUIScene(); + private readonly completeUi = new GameCompleteUIScene(); + private readonly gameOverUi = new GameOverUIScene(); + private game?: GameScene; + private lastFrame = performance.now(); + + async boot(): Promise { + if (!this.container) throw new Error('Missing #game-container'); + await this.preloader.load(); + this.title.show(() => this.start()); + window.addEventListener('resize', () => this.game?.resize()); + window.addEventListener('keydown', (event) => { + if (event.code === 'Escape' && this.game) { + if (this.game.isPaused()) this.resume('GameScene'); + else this.pause('GameScene'); + } + }); + requestAnimationFrame(this.frame); + } + + private start(): void { + if (!this.container) return; + this.game = new GameScene( + this.container, + { + onProgress: (collected, total) => this.ui.update(collected, total), + onComplete: () => { + this.game?.setPaused(true); + this.completeUi.show(() => location.reload()); + }, + onGameOver: () => { + this.game?.setPaused(true); + this.gameOverUi.show(() => location.reload()); + }, + }, + this.preloader.textures, + ); + this.ui.init({ gameSceneKey: 'GameScene' }); + this.ui.show((key) => this.pause(key)); + window.__opengame3d = { + isPaused: () => this.game?.isPaused() ?? false, + renderer: 'three.js', + }; + } + + private pause(sceneKey: string): void { + if (sceneKey !== 'GameScene' || !this.game || this.game.isPaused()) return; + this.game.setPaused(true); + this.pauseUi.init({ gameSceneKey: sceneKey }); + this.pauseUi.show((key) => this.resume(key)); + } + + private resume(sceneKey: string): void { + if (sceneKey === 'GameScene') { + this.pauseUi.hide(); + this.game?.setPaused(false); + } + } + + private readonly frame = (time: number): void => { + const delta = Math.min(0.05, (time - this.lastFrame) / 1000); + this.lastFrame = time; + this.game?.update(delta); + requestAnimationFrame(this.frame); + }; +} + +new GameApp().boot(); diff --git a/agent-test/templates/core3d/src/scenes/GameCompleteUIScene.ts b/agent-test/templates/core3d/src/scenes/GameCompleteUIScene.ts new file mode 100644 index 000000000..e58889337 --- /dev/null +++ b/agent-test/templates/core3d/src/scenes/GameCompleteUIScene.ts @@ -0,0 +1,10 @@ +export class GameCompleteUIScene { + show(onRestart: () => void): void { + const root = document.getElementById('ui-root'); + if (!root) return; + root.innerHTML = `

TRAIL COMPLETE

All energy recovered.

`; + root.querySelector('#complete-btn')?.addEventListener('click', onRestart, { + once: true, + }); + } +} diff --git a/agent-test/templates/core3d/src/scenes/GameOverUIScene.ts b/agent-test/templates/core3d/src/scenes/GameOverUIScene.ts new file mode 100644 index 000000000..222b818c0 --- /dev/null +++ b/agent-test/templates/core3d/src/scenes/GameOverUIScene.ts @@ -0,0 +1,10 @@ +export class GameOverUIScene { + show(onRestart: () => void): void { + const root = document.getElementById('ui-root'); + if (!root) return; + root.innerHTML = `

GAME OVER

`; + root.querySelector('#retry-btn')?.addEventListener('click', onRestart, { + once: true, + }); + } +} diff --git a/agent-test/templates/core3d/src/scenes/PauseUIScene.ts b/agent-test/templates/core3d/src/scenes/PauseUIScene.ts new file mode 100644 index 000000000..a26e94dff --- /dev/null +++ b/agent-test/templates/core3d/src/scenes/PauseUIScene.ts @@ -0,0 +1,44 @@ +import { LevelManager } from '../LevelManager'; +import type { SceneKeyData } from './UIScene'; + +export class PauseUIScene { + private sceneKey = LevelManager.getFirstLevelScene(); + private keyHandler?: (event: KeyboardEvent) => void; + + init(data: SceneKeyData = {}): void { + this.sceneKey = + data.gameSceneKey ?? + data.currentLevelKey ?? + LevelManager.getFirstLevelScene(); + } + + show(onResume: (sceneKey: string) => void): void { + const root = document.getElementById('ui-root'); + if (!root) return; + root.insertAdjacentHTML( + 'beforeend', + `
+

GAME PAUSED

+
`, + ); + const resume = () => { + this.hide(); + onResume(this.sceneKey); + }; + root.querySelector('#resume-btn')?.addEventListener('click', resume, { + once: true, + }); + this.keyHandler = (event) => { + if (event.code === 'Space' || event.code === 'Enter') { + event.stopPropagation(); + resume(); + } + }; + document.addEventListener('keydown', this.keyHandler); + } + + hide(): void { + if (this.keyHandler) document.removeEventListener('keydown', this.keyHandler); + document.getElementById('pause-overlay')?.remove(); + } +} diff --git a/agent-test/templates/core3d/src/scenes/Preloader.ts b/agent-test/templates/core3d/src/scenes/Preloader.ts new file mode 100644 index 000000000..5c283ec02 --- /dev/null +++ b/agent-test/templates/core3d/src/scenes/Preloader.ts @@ -0,0 +1,48 @@ +import { LoadingManager, Texture, TextureLoader } from 'three'; + +export class Preloader { + readonly textures = new Map(); + + async load(entries?: Record): Promise { + const assetEntries = entries ?? (await this.readAssetPack()); + const manager = new LoadingManager(); + const loader = new TextureLoader(manager); + const loads = Object.entries(assetEntries).map( + ([key, url]) => + new Promise((resolve) => { + loader.load( + url, + (texture) => { + this.textures.set(key, texture); + resolve(); + }, + undefined, + () => resolve(), + ); + }), + ); + await Promise.all(loads); + } + + private async readAssetPack(): Promise> { + try { + const response = await fetch('assets/asset-pack.json'); + if (!response.ok) return {}; + const pack = (await response.json()) as Record< + string, + { files?: Array<{ type?: string; key?: string; url?: string }> } + >; + const entries: Record = {}; + for (const section of Object.values(pack)) { + for (const file of section.files ?? []) { + if (file.type === 'image' && file.key && file.url) { + entries[file.key] = file.url; + } + } + } + return entries; + } catch { + return {}; + } + } +} diff --git a/agent-test/templates/core3d/src/scenes/TitleScreen.ts b/agent-test/templates/core3d/src/scenes/TitleScreen.ts new file mode 100644 index 000000000..47e46e008 --- /dev/null +++ b/agent-test/templates/core3d/src/scenes/TitleScreen.ts @@ -0,0 +1,33 @@ +export class TitleScreen { + private keyHandler?: (event: KeyboardEvent) => void; + + show(onStart: () => void): void { + const root = document.getElementById('ui-root'); + if (!root) return; + root.innerHTML = ` +
+
+

OPENGAME 3D

+

PRISM TRAIL

+ +
+
`; + const start = () => { + this.hide(); + onStart(); + }; + root.querySelector('#start-btn')?.addEventListener('click', start, { + once: true, + }); + this.keyHandler = (event) => { + if (event.code === 'Enter' || event.code === 'Space') start(); + }; + document.addEventListener('keydown', this.keyHandler); + } + + hide(): void { + if (this.keyHandler) document.removeEventListener('keydown', this.keyHandler); + const root = document.getElementById('ui-root'); + if (root) root.innerHTML = ''; + } +} diff --git a/agent-test/templates/core3d/src/scenes/UIScene.ts b/agent-test/templates/core3d/src/scenes/UIScene.ts new file mode 100644 index 000000000..ea7571e53 --- /dev/null +++ b/agent-test/templates/core3d/src/scenes/UIScene.ts @@ -0,0 +1,39 @@ +import { LevelManager } from '../LevelManager'; + +export type SceneKeyData = { + gameSceneKey?: string; + currentLevelKey?: string; +}; + +export class UIScene { + private sceneKey = LevelManager.getFirstLevelScene(); + + init(data: SceneKeyData = {}): void { + this.sceneKey = + data.gameSceneKey ?? + data.currentLevelKey ?? + LevelManager.getFirstLevelScene(); + } + + show(onPause: (sceneKey: string) => void): void { + const root = document.getElementById('ui-root'); + if (!root) return; + root.innerHTML = ` +
+
+
ENERGY
+
0 / 0
+
+ +
WASD / ARROWS: MOVE · DRAG: LOOK · ESC: PAUSE
+
`; + root.querySelector('#pause-btn')?.addEventListener('click', () => + onPause(this.sceneKey), + ); + } + + update(collected: number, total: number): void { + const score = document.getElementById('score-text'); + if (score) score.textContent = `${collected} / ${total}`; + } +} diff --git a/agent-test/templates/core3d/src/styles/tailwind.css b/agent-test/templates/core3d/src/styles/tailwind.css new file mode 100644 index 000000000..0c697a00e --- /dev/null +++ b/agent-test/templates/core3d/src/styles/tailwind.css @@ -0,0 +1,19 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body, +#game-container { + width: 100%; + height: 100%; + margin: 0; + overflow: hidden; + background: #030712; +} + +canvas { + display: block; + width: 100%; + height: 100%; +} diff --git a/agent-test/templates/core3d/tailwind.config.js b/agent-test/templates/core3d/tailwind.config.js new file mode 100644 index 000000000..4a6eff983 --- /dev/null +++ b/agent-test/templates/core3d/tailwind.config.js @@ -0,0 +1,10 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{ts,html}'], + theme: { + extend: { + fontFamily: { retro: ['ui-monospace', 'monospace'] }, + }, + }, + plugins: [], +}; diff --git a/agent-test/templates/core3d/tsconfig.json b/agent-test/templates/core3d/tsconfig.json new file mode 100644 index 000000000..1d114120b --- /dev/null +++ b/agent-test/templates/core3d/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "include": ["src"] +} diff --git a/agent-test/templates/core3d/vite.config.js b/agent-test/templates/core3d/vite.config.js new file mode 100644 index 000000000..fcfef3886 --- /dev/null +++ b/agent-test/templates/core3d/vite.config.js @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + base: './', + server: { host: '127.0.0.1' }, +}); diff --git a/agent-test/templates/modules/threed_basic/src/GameScene.ts b/agent-test/templates/modules/threed_basic/src/GameScene.ts new file mode 100644 index 000000000..c9878650e --- /dev/null +++ b/agent-test/templates/modules/threed_basic/src/GameScene.ts @@ -0,0 +1,173 @@ +import { + CircleGeometry, + Color, + EquirectangularReflectionMapping, + IcosahedronGeometry, + Mesh, + MeshStandardMaterial, + Object3D, + PerspectiveCamera, + Scene, + SphereGeometry, + Sprite, + SpriteMaterial, + SRGBColorSpace, + Texture, + Vector3, + WebGLRenderer, +} from 'three'; +import gameConfig from './gameConfig.json'; +import { InputController } from './InputController'; +import { initSceneMap } from './SceneMap'; +import { applyThreeSceneDefaults } from './ThreeSceneDefaults'; + +export type GameSceneEvents = { + onProgress: (collected: number, total: number) => void; + onComplete: () => void; + onGameOver: () => void; +}; + +export class GameScene { + readonly renderer: WebGLRenderer; + private readonly scene = new Scene(); + private readonly camera = new PerspectiveCamera(65, 1, 0.1, 200); + private readonly input: InputController; + private readonly collectibles: Object3D[] = []; + private paused = false; + private completed = false; + private collected = 0; + private yaw = 0; + + constructor( + private readonly container: HTMLElement, + private readonly events: GameSceneEvents, + private readonly textures: Map = new Map(), + ) { + this.renderer = new WebGLRenderer({ antialias: true, preserveDrawingBuffer: true }); + this.renderer.outputColorSpace = SRGBColorSpace; + this.renderer.setPixelRatio( + Math.min(devicePixelRatio, gameConfig.renderConfig.pixelRatioCap.value), + ); + this.renderer.setSize(container.clientWidth, container.clientHeight); + container.replaceChildren(this.renderer.domElement); + this.input = new InputController(this.renderer.domElement); + this.buildWorld(); + this.resize(); + this.events.onProgress(0, this.collectibles.length); + } + + private buildWorld(): void { + applyThreeSceneDefaults(this.scene); + const skybox = this.textures.get('skybox_texture'); + if (skybox) { + skybox.mapping = EquirectangularReflectionMapping; + skybox.colorSpace = SRGBColorSpace; + this.scene.background = skybox; + } + + const map = initSceneMap(); + const floorTexture = this.textures.get('floor_patch'); + if (floorTexture) floorTexture.colorSpace = SRGBColorSpace; + const floorMaterial = new MeshStandardMaterial({ + color: floorTexture ? 0xffffff : 0x123c66, + map: floorTexture, + roughness: 0.92, + }); + for (const patch of map.floorPatches) { + const floor = new Mesh( + new CircleGeometry(patch.radius, 20), + floorMaterial, + ); + floor.rotation.x = -Math.PI / 2; + floor.position.set(patch.x, 0, patch.z); + this.scene.add(floor); + } + + const obstacleMaterial = new MeshStandardMaterial({ + color: 0x7c3aed, + emissive: new Color(0x21084f), + flatShading: true, + }); + for (const obstacle of map.obstacles) { + const mesh = new Mesh(new IcosahedronGeometry(obstacle.scale, 0), obstacleMaterial); + mesh.position.set(obstacle.x, obstacle.y, obstacle.z); + this.scene.add(mesh); + } + + const energyTexture = this.textures.get('energy_billboard'); + if (energyTexture) energyTexture.colorSpace = SRGBColorSpace; + for (const item of map.collectibles) { + const collectible = energyTexture + ? new Sprite(new SpriteMaterial({ map: energyTexture, transparent: true })) + : new Mesh( + new SphereGeometry(0.55, 12, 8), + new MeshStandardMaterial({ color: 0x67e8f9, emissive: 0x155e75 }), + ); + collectible.name = item.id; + collectible.position.set(item.x, item.y, item.z); + collectible.scale.setScalar(energyTexture ? 1.8 : 1); + this.collectibles.push(collectible); + this.scene.add(collectible); + } + this.camera.position.set(0, 2.2, 7); + } + + update(deltaSeconds: number): void { + if (this.paused || this.completed) return; + this.yaw -= + this.input.consumeLookDelta() * gameConfig.playerConfig.mouseSensitivity.value; + const { forward, strafe } = this.input.movement(); + const speed = gameConfig.playerConfig.moveSpeed.value * deltaSeconds; + this.camera.position.x += + (Math.cos(this.yaw) * strafe + Math.sin(this.yaw) * forward) * speed; + this.camera.position.z += + (Math.sin(this.yaw) * strafe - Math.cos(this.yaw) * forward) * speed; + this.camera.position.x = Math.max( + -gameConfig.levelConfig.trackHalfWidth.value, + Math.min(gameConfig.levelConfig.trackHalfWidth.value, this.camera.position.x), + ); + this.camera.position.z = Math.max( + gameConfig.levelConfig.finishZ.value, + Math.min(8, this.camera.position.z), + ); + this.camera.rotation.y = this.yaw; + + for (const collectible of [...this.collectibles]) { + collectible.rotation.y += deltaSeconds * 2; + if ( + collectible.position.distanceTo(this.camera.position) <= + gameConfig.levelConfig.collectRadius.value + ) { + this.scene.remove(collectible); + this.collectibles.splice(this.collectibles.indexOf(collectible), 1); + this.collected++; + this.events.onProgress(this.collected, this.collected + this.collectibles.length); + } + } + if (this.collectibles.length === 0) { + this.completed = true; + this.events.onComplete(); + } + this.renderer.render(this.scene, this.camera); + } + + setPaused(paused: boolean): void { + this.paused = paused; + this.input.clear(); + } + + isPaused(): boolean { + return this.paused; + } + + resize(): void { + const size = new Vector3( + Math.max(1, this.container.clientWidth), + Math.max(1, this.container.clientHeight), + 0, + ); + this.camera.aspect = size.x / size.y; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(size.x, size.y); + } +} diff --git a/agent-test/templates/modules/threed_basic/src/InputController.ts b/agent-test/templates/modules/threed_basic/src/InputController.ts new file mode 100644 index 000000000..97b237519 --- /dev/null +++ b/agent-test/templates/modules/threed_basic/src/InputController.ts @@ -0,0 +1,55 @@ +export class InputController { + private readonly keys = new Set(); + private dragging = false; + private lookDelta = 0; + + constructor(canvas: HTMLCanvasElement) { + window.addEventListener('keydown', this.onKeyDown); + window.addEventListener('keyup', this.onKeyUp); + canvas.addEventListener('mousedown', this.onMouseDown); + window.addEventListener('mouseup', this.onMouseUp); + window.addEventListener('mousemove', this.onMouseMove); + } + + movement(): { forward: number; strafe: number } { + return { + forward: + Number(this.keys.has('KeyW') || this.keys.has('ArrowUp')) - + Number(this.keys.has('KeyS') || this.keys.has('ArrowDown')), + strafe: + Number(this.keys.has('KeyD') || this.keys.has('ArrowRight')) - + Number(this.keys.has('KeyA') || this.keys.has('ArrowLeft')), + }; + } + + consumeLookDelta(): number { + const delta = this.lookDelta; + this.lookDelta = 0; + return delta; + } + + clear(): void { + this.keys.clear(); + this.lookDelta = 0; + } + + private readonly onKeyDown = (event: KeyboardEvent): void => { + this.keys.add(event.code); + }; + + private readonly onKeyUp = (event: KeyboardEvent): void => { + this.keys.delete(event.code); + }; + + private readonly onMouseDown = (): void => { + this.dragging = true; + }; + + private readonly onMouseUp = (): void => { + this.dragging = false; + }; + + private readonly onMouseMove = (event: MouseEvent): void => { + if (this.dragging) this.lookDelta += event.movementX; + }; +} diff --git a/agent-test/templates/modules/threed_basic/src/SceneMap.ts b/agent-test/templates/modules/threed_basic/src/SceneMap.ts new file mode 100644 index 000000000..edbafd736 --- /dev/null +++ b/agent-test/templates/modules/threed_basic/src/SceneMap.ts @@ -0,0 +1,29 @@ +export type SceneMap = { + floorPatches: Array<{ x: number; z: number; radius: number }>; + collectibles: Array<{ id: string; x: number; y: number; z: number }>; + obstacles: Array<{ x: number; y: number; z: number; scale: number }>; +}; + +/** Editor-facing data initialization; keep positions declarative and code-free. */ +export function initSceneMap(): SceneMap { + return { + // EXT: append authored path patches without changing GameScene. + floorPatches: Array.from({ length: 14 }, (_, i) => ({ + x: Math.sin(i * 0.8) * 2.2, + z: 2 - i * 6, + radius: 5.2, + })), + collectibles: Array.from({ length: 8 }, (_, i) => ({ + id: `energy-${i + 1}`, + x: Math.sin(i * 1.4) * 3.5, + y: 1.35, + z: -5 - i * 9, + })), + obstacles: Array.from({ length: 12 }, (_, i) => ({ + x: i % 2 ? 5.6 : -5.6, + y: 1.2, + z: -i * 6, + scale: 0.7 + (i % 3) * 0.25, + })), + }; +} diff --git a/agent-test/templates/modules/threed_basic/src/ThreeSceneDefaults.ts b/agent-test/templates/modules/threed_basic/src/ThreeSceneDefaults.ts new file mode 100644 index 000000000..900b08538 --- /dev/null +++ b/agent-test/templates/modules/threed_basic/src/ThreeSceneDefaults.ts @@ -0,0 +1,15 @@ +import { AmbientLight, Color, DirectionalLight, Fog, Scene } from 'three'; +import gameConfig from './gameConfig.json'; + +export function applyThreeSceneDefaults(scene: Scene): void { + scene.background = new Color(0x060b21); + scene.fog = new Fog( + 0x060b21, + gameConfig.renderConfig.fogNear.value, + gameConfig.renderConfig.fogFar.value, + ); + scene.add(new AmbientLight(0x7dd3fc, 1.6)); + const keyLight = new DirectionalLight(0xffffff, 3.2); + keyLight.position.set(8, 16, 10); + scene.add(keyLight); +} diff --git a/agent-test/templates/modules/threed_basic/src/gameConfig.json b/agent-test/templates/modules/threed_basic/src/gameConfig.json new file mode 100644 index 000000000..1ec3b8a9f --- /dev/null +++ b/agent-test/templates/modules/threed_basic/src/gameConfig.json @@ -0,0 +1,20 @@ +{ + "screenSize": { + "width": { "value": 1152, "type": "number", "description": "Reference width" }, + "height": { "value": 768, "type": "number", "description": "Reference height" } + }, + "renderConfig": { + "pixelRatioCap": { "value": 2, "type": "number", "description": "Renderer pixel ratio cap" }, + "fogNear": { "value": 16, "type": "number", "description": "Fog near distance" }, + "fogFar": { "value": 82, "type": "number", "description": "Fog far distance" } + }, + "playerConfig": { + "moveSpeed": { "value": 9, "type": "number", "description": "Movement units per second" }, + "mouseSensitivity": { "value": 0.002, "type": "number", "description": "Mouse look sensitivity" } + }, + "levelConfig": { + "trackHalfWidth": { "value": 7, "type": "number", "description": "Half-width of the playable track" }, + "finishZ": { "value": -78, "type": "number", "description": "End of the main path" }, + "collectRadius": { "value": 1.8, "type": "number", "description": "Manual pickup radius" } + } +} diff --git a/packages/core/src/tools/game-type-classifier.test.ts b/packages/core/src/tools/game-type-classifier.test.ts new file mode 100644 index 000000000..2f7cf8890 --- /dev/null +++ b/packages/core/src/tools/game-type-classifier.test.ts @@ -0,0 +1,60 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import type { Config } from '../config/config.js'; +import { GameTypeClassifierTool } from './game-type-classifier.js'; + +const modelConfig = { + apiKey: 'test-key', + baseUrl: 'https://classifier.test/v1', + modelName: 'test-model', +}; + +afterEach(() => vi.unstubAllGlobals()); + +describe('GameTypeClassifierTool archetype routing', () => { + it.each([ + [ + '做一个 three.js 迷宫漫游游戏', + 'threed_basic', + 'three_dimensional', + 'core3d', + ], + ['做一个 2D Phaser 横版跳跃游戏', 'platformer', 'side', 'core'], + ['做一个 2D Phaser 俯视角自由移动游戏', 'top_down', 'top_down', 'core'], + ])( + 'routes %s to %s without changing the 2D core', + async (gameDescription, archetype, perspective, coreTemplate) => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ + choices: [ + { + message: { + content: JSON.stringify({ + archetype, + reasoning: 'test fixture', + physicsProfile: { + hasGravity: archetype === 'platformer', + perspective, + movementType: 'continuous', + }, + }), + }, + }, + ], + }), + }), + ); + + const tool = new GameTypeClassifierTool({} as Config, modelConfig); + const result = await tool + .build({ game_description: gameDescription }) + .execute(new AbortController().signal); + + expect(result.error).toBeUndefined(); + expect(result.llmContent).toContain(`Archetype: ${archetype}`); + expect(result.llmContent).toContain(`/templates/${coreTemplate}/*`); + }, + ); +}); diff --git a/packages/core/src/tools/game-type-classifier.ts b/packages/core/src/tools/game-type-classifier.ts index b81cf2f0a..9107f5768 100644 --- a/packages/core/src/tools/game-type-classifier.ts +++ b/packages/core/src/tools/game-type-classifier.ts @@ -44,14 +44,15 @@ export type GameArchetype = | 'top_down' | 'grid_logic' | 'tower_defense' - | 'ui_heavy'; + | 'ui_heavy' + | 'threed_basic'; export interface ClassificationResult { archetype: GameArchetype; reasoning: string; physicsProfile: { hasGravity: boolean; - perspective: 'side' | 'top_down' | 'none'; + perspective: 'side' | 'top_down' | 'three_dimensional' | 'none'; movementType: 'continuous' | 'grid' | 'path' | 'ui_only'; }; } @@ -173,12 +174,21 @@ You are a game physics analyzer. Your job is to classify games based on their PH **Key Question**: Is the game primarily UI panels and state changes? +### 6. threed_basic (Explicit 3D World) +**Physics**: No physics engine; simple geometry and manual collision bounds +**Perspective**: First-person, third-person, or freely moving camera in a three-dimensional world +**Movement**: Keyboard movement plus mouse look/steering +**Examples**: three.js maze roaming, low-poly floating-island treasure hunt, neon 3D collection runner + +**Key Question**: Does the request explicitly require 3D, three.js, a 3D camera, or movement through a three-dimensional world? +**Priority Rule**: Explicit 3D intent wins over genre words such as maze, platformer, runner, or racing. Do not classify ordinary 2D Phaser games as threed_basic. + ## Output Format Respond with ONLY a JSON object (no markdown, no explanation outside JSON): { - "archetype": "platformer" | "top_down" | "grid_logic" | "tower_defense" | "ui_heavy", + "archetype": "platformer" | "top_down" | "grid_logic" | "tower_defense" | "ui_heavy" | "threed_basic", "reasoning": "Brief explanation of why this archetype was chosen based on physics", "physicsProfile": { "hasGravity": true | false, @@ -194,6 +204,8 @@ Respond with ONLY a JSON object (no markdown, no explanation outside JSON): - Hill Climb Racing is NOT top_down (it has gravity, it's platformer) - SimCity/Factorio are grid_logic (grid-based building), not top_down - Racing games: If side-view with gravity = platformer, if top-down = top_down +- A "three.js maze roaming" request is threed_basic, not grid_logic or top_down +- A normal 2D side-view Phaser platformer remains platformer `; } @@ -202,7 +214,7 @@ Respond with ONLY a JSON object (no markdown, no explanation outside JSON): "${this.params.game_description}" -Remember: Think about GRAVITY, PERSPECTIVE, and MOVEMENT TYPE. Output JSON only.`; +Remember: Explicit 3D/three.js intent takes priority; otherwise use GRAVITY, PERSPECTIVE, and MOVEMENT TYPE. Output JSON only.`; } private async callClassifierModel( @@ -280,6 +292,7 @@ Remember: Think about GRAVITY, PERSPECTIVE, and MOVEMENT TYPE. Output JSON only. } catch { // Fallback: try to extract archetype from text const archetypes: GameArchetype[] = [ + 'threed_basic', 'platformer', 'top_down', 'grid_logic', @@ -293,7 +306,12 @@ Remember: Think about GRAVITY, PERSPECTIVE, and MOVEMENT TYPE. Output JSON only. reasoning: result, physicsProfile: { hasGravity: arch === 'platformer', - perspective: arch === 'platformer' ? 'side' : 'top_down', + perspective: + arch === 'threed_basic' + ? 'three_dimensional' + : arch === 'platformer' + ? 'side' + : 'top_down', movementType: arch === 'grid_logic' ? 'grid' : 'continuous', }, }; @@ -317,6 +335,9 @@ Remember: Think about GRAVITY, PERSPECTIVE, and MOVEMENT TYPE. Output JSON only. const templatesDir = process.env.GAME_TEMPLATES_DIR || '../../templates'; const docsDir = process.env.GAME_DOCS_DIR || '../../docs'; + const coreTemplate = + result.archetype === 'threed_basic' ? 'core3d' : 'core'; + return ` Archetype: ${result.archetype} Reasoning: ${result.reasoning} @@ -330,13 +351,13 @@ Physics Profile: GAME TYPE CLASSIFIED: **${result.archetype}** -## Next Step: Scaffold Templates (FOUR commands) +## Next Step: Scaffold Templates Run these commands NOW: \`\`\`bash # Step 1: Copy core template (creates src/, public/, config files) -cp -r ${templatesDir}/core/* ./ +cp -r ${templatesDir}/${coreTemplate}/* ./ # Step 2: Copy module-specific code INTO src/ (ADDITIVE merge) cp -r ${templatesDir}/modules/${result.archetype}/src/* ./src/ @@ -349,6 +370,9 @@ cp ${docsDir}/asset_protocol.md ${docsDir}/debug_protocol.md docs/ # Step 4: Copy module-specific documentation mkdir -p docs/modules/${result.archetype} cp -r ${docsDir}/modules/${result.archetype}/* docs/modules/${result.archetype}/ + +# three.js is pinned in core3d/package-lock.json; install only inside this game workspace. +if [ "${result.archetype}" = "threed_basic" ]; then npm ci; fi \`\`\` ## After Scaffolding: Proceed to Phase 2 (GDD Generation) @@ -369,6 +393,7 @@ Next: Call \`generate-gdd\` tool with: grid_logic: 'Grid + Static Logic (Sokoban, Fire Emblem, Match-3)', tower_defense: 'Path + Waves (Kingdom Rush, Bloons TD)', ui_heavy: 'UI Driven (Card Games, Visual Novels, Idle Clickers)', + threed_basic: '3D World + Manual Movement (three.js primitives)', }; return `**Game Type Classification** @@ -422,7 +447,7 @@ export class GameTypeClassifierTool extends BaseDeclarativeTool< super( GameTypeClassifierTool.Name, ToolDisplayNames.GAME_TYPE_CLASSIFIER, - `Classifies a game idea into an archetype based on PHYSICS and PERSPECTIVE (not genre name). Returns: platformer (side+gravity), top_down (free movement), grid_logic (discrete tiles), tower_defense (path+waves), or ui_heavy (no physics). Call this FIRST before scaffolding templates.`, + `Classifies a game idea into an archetype based on DIMENSION, PHYSICS, and PERSPECTIVE (not genre name). Returns: platformer (side+gravity), top_down (free movement), grid_logic (discrete tiles), tower_defense (path+waves), ui_heavy (no physics), or threed_basic (explicit three.js/3D world). Call this FIRST before scaffolding templates.`, Kind.Think, { type: 'object', diff --git a/packages/core/src/tools/generate-gdd.ts b/packages/core/src/tools/generate-gdd.ts index 2c8708d52..2bb20eb48 100644 --- a/packages/core/src/tools/generate-gdd.ts +++ b/packages/core/src/tools/generate-gdd.ts @@ -20,7 +20,8 @@ export type GameArchetype = | 'top_down' | 'grid_logic' | 'tower_defense' - | 'ui_heavy'; + | 'ui_heavy' + | 'threed_basic'; export interface GenerateGDDParams { /** @@ -111,11 +112,11 @@ Save content between tags to \`GAME_DESIGN.md\` ### Phase 3: Assets (use GDD Section 1) - Read \`{DOCS_DIR}/asset_protocol.md\` - Call \`generate_game_assets\` with the Asset Registry table from **GDD Section 1** -- Call \`generate_tilemap\` with ASCII maps from **GDD Section 4** (NOT for ui_heavy, tower_defense, or grid_logic -- these use code-defined grids) +- Call \`generate_tilemap\` with ASCII maps from **GDD Section 4** (NOT for ui_heavy, tower_defense, grid_logic, or threed_basic) - Read \`public/assets/asset-pack.json\` for generated texture keys ### Phase 4: Config (use GDD Section 2) -- MERGE GDD Section 2 values INTO the existing \`src/gameConfig.json\` -- add/update game-specific fields using \`{ "value": X }\` wrapper format, but NEVER delete infrastructure fields (\`screenSize\`, \`debugConfig\`, \`renderConfig\`) +- MERGE GDD Section 2 values INTO the existing \`src/gameConfig.json\` -- add/update game-specific fields using \`{ "value": X }\` wrapper format, but NEVER delete infrastructure fields (\`screenSize\`, \`renderConfig\`, and Phaser's \`debugConfig\`) ### Phase 5: Code Implementation (use GDD Sections 0, 3, 5) - **GDD Section 0** has scene keys -> update \`LevelManager.ts\` and \`main.ts\` @@ -623,6 +624,19 @@ Tower Defense does NOT use generate_tilemap -- maps are code-defined grids. - Do NOT invent new hooks (e.g., onRoundStart, onBuzzerPressed, onAnswerSelected, onTimeout do NOT exist) - If template_api.md doesn't list a hook, it DOES NOT EXIST -- design around existing hooks only +`, + threed_basic: `--- + +## Three.js Basic Rules (Built-in) + +- Runtime: pinned three.js + TypeScript + Vite; do not import Phaser. +- World: primitives/custom low-poly geometry, one PerspectiveCamera, ambient + directional light, fog, and a sky texture. +- Input: WASD/arrows + mouse; ESC uses the existing DOM pause contract. No touch controls. +- Gameplay: one short route, 5-10 collectible objectives, one reachable completion state. +- Collision: manual bounds/proximity only. Do not add a physics engine. +- Assets: use generate_game_assets only for skybox/texture/billboard/floor patch images. No models, GLB/FBX/OBJ, text-to-3D, or generate_tilemap. +- Architecture: keep main.ts RAF wiring; put declarative positions in SceneMap.ts and rendering/gameplay in GameScene.ts. +- Required verification: build, WebGL context, non-black canvas, zero console errors, and ESC pause/resume. `, }; @@ -711,6 +725,12 @@ ${this.getSection4Guidance(archetype)}`; { "type": "image", "key": "icon_tower_tabby", "description": "Icon for Spitfire Tabby tower selection UI: small version of tabby cat" } { "type": "image", "key": "tower_slot", "description": "A round stone platform with subtle moss, top-down view" } \`\`\``, + threed_basic: `**Three.js image asset rules:** +- Skybox: \`type: "background"\`, key \`skybox_texture\`, equirectangular, \`resolution: "1024*1024"\`. +- Floor patch/surface: \`type: "image"\`, key \`floor_patch\`; declare runtime \`displaySize\` in the GDD, never pass it to the tool. +- Collectible billboard: \`type: "image"\`, key \`energy_billboard\`, one centered subject. +- Keep source/display size <= 1024*1024. Colormap only matte art; never glossy/translucent/emissive/sky art. +- Every generated image must remain in asset-pack.json. Do not request models, meshes, normal maps, or text-to-3D output.`, }; return guidance[archetype] || ''; } @@ -722,6 +742,7 @@ ${this.getSection4Guidance(archetype)}`; top_down: 'Entity Architecture (Behavior Composition)', grid_logic: 'Entity Architecture (Grid Logic)', tower_defense: 'Entity Architecture (Towers & Waves)', + threed_basic: '3D Scene Architecture (SceneMap + GameScene)', }; return titles[archetype] || 'Entity Architecture'; } @@ -867,6 +888,9 @@ Refer to Design Guide Section 7 for screen shake rules, Template Capabilities Se - \`getCellsInRadius(x, y, radius, w, h)\`: cells in area (AoE effects) **CRITICAL**: Every hook name must exist in template_api.md. Do NOT invent hooks.`, + threed_basic: `Define the exact \`SceneMap.ts\` arrays (floorPatches, collectibles, obstacles), the \`GameScene\` texture-key mapping, camera start/bounds, and completion condition. + +Use only the public constructor and methods from template_api.md: \`update\`, \`setPaused\`, \`isPaused\`, and \`resize\`. Preserve main.ts RAF and DOM-screen wiring. List exact config fields and values; do not invent editor/runtime hooks.`, tower_defense: `Define every tower type, enemy type, and level scene with EXACT configurations. This maps directly to code files. **For each Tower Type** (COPY \`_TemplateTower.ts\` -> \`TowerName.ts\`): @@ -932,6 +956,7 @@ if (slowAmt && slowDur) enemy.applyStatusEffect('slow', slowAmt, slowDur, 0x4488 top_down: 'Level Design', grid_logic: 'Level & Puzzle Design (Code-Defined Grid)', tower_defense: 'Map & Wave Design', + threed_basic: 'Single-Route 3D Level Map', }; return titles[archetype] || 'Level Design'; } @@ -1161,6 +1186,14 @@ List which tower types are available in each level. Early levels may restrict to - Starting gold: 80-200 (higher = easier) - Kill rewards: 5-50g per enemy type - Wave clear bonuses: 10-100g per wave`, + threed_basic: `**Do not use generate_tilemap.** Provide one declarative route for \`initSceneMap()\`: +- 8-14 overlapping floor patches with x/z/radius values +- 5-10 reachable collectibles with id/x/y/z values +- 8-16 low-poly obstacle decorations outside the main walking line +- camera start, track bounds, pickup radius, and finish z +- a manual play path proving every collectible is reachable + +End Section 4 with: \`3D scope: primitives + generated image textures; no model generation\`.`, }; return guidance[archetype] || ''; } @@ -1271,6 +1304,7 @@ export class GenerateGDDTool extends BaseDeclarativeTool< 'grid_logic', 'tower_defense', 'ui_heavy', + 'threed_basic', ], }, config_summary: { @@ -1306,6 +1340,7 @@ export class GenerateGDDTool extends BaseDeclarativeTool< 'grid_logic', 'tower_defense', 'ui_heavy', + 'threed_basic', ]; if (!validArchetypes.includes(params.archetype)) { return `Invalid archetype: ${params.archetype}. Must be one of: ${validArchetypes.join(', ')}`;