clone

an rts arena for scripts: submit tribe strategies over the web or mcp, run deterministic matches, watch them

initial: tribes fishbowl — deterministic engine, QuickJS script host, worker (REST+MCP+cron), web viewer, examples, tribes.pub deploy tooling

Dami committed 097b164
commit
097b164134c9b0ac2c218f03d807cf190e57a533
author
Dami <dami.etoile@gmail.com>
126 files changed, +19983 -0
A.gitignore8+
A.playwright-cli/console-2026-08-15T22-13-49-782Z.log1+
A.playwright-cli/console-2026-08-15T22-17-57-884Z.log1+
A.playwright-cli/console-2026-08-15T22-20-31-746Z.log1+
A.playwright-cli/console-2026-08-15T22-21-53-096Z.log12+
A.playwright-cli/console-2026-08-15T22-24-44-466Z.log15+
A.playwright-cli/console-2026-08-15T22-26-13-273Z.log19+
A.playwright-cli/console-2026-08-15T22-29-39-637Z.log29+
A.playwright-cli/console-2026-08-15T22-31-24-554Z.log1+
A.playwright-cli/console-2026-08-15T22-49-50-836Z.log1+
A.playwright-cli/page-2026-08-15T22-13-50-011Z.yml9+
A.playwright-cli/page-2026-08-15T22-14-02-478Z.pngbin
A.playwright-cli/page-2026-08-15T22-14-24-319Z.pngbin
A.playwright-cli/page-2026-08-15T22-15-05-450Z.yml72+
A.playwright-cli/page-2026-08-15T22-15-28-885Z.yml98+
A.playwright-cli/page-2026-08-15T22-15-39-139Z.yml98+
A.playwright-cli/page-2026-08-15T22-17-58-166Z.yml9+
A.playwright-cli/page-2026-08-15T22-18-07-896Z.yml72+
A.playwright-cli/page-2026-08-15T22-18-30-653Z.yml98+
A.playwright-cli/page-2026-08-15T22-18-32-845Z.yml98+
A.playwright-cli/page-2026-08-15T22-20-32-055Z.yml9+
A.playwright-cli/page-2026-08-15T22-21-09-352Z.yml72+
A.playwright-cli/page-2026-08-15T22-21-53-393Z.yml9+
A.playwright-cli/page-2026-08-15T22-22-06-251Z.yml72+
A.playwright-cli/page-2026-08-15T22-23-02-712Z.yml72+
A.playwright-cli/page-2026-08-15T22-23-22-458Z.yml72+
A.playwright-cli/page-2026-08-15T22-23-51-018Z.yml98+
A.playwright-cli/page-2026-08-15T22-24-44-702Z.yml9+
A.playwright-cli/page-2026-08-15T22-24-55-903Z.yml72+
A.playwright-cli/page-2026-08-15T22-25-17-559Z.yml98+
A.playwright-cli/page-2026-08-15T22-26-13-505Z.yml9+
A.playwright-cli/page-2026-08-15T22-26-26-954Z.yml72+
A.playwright-cli/page-2026-08-15T22-26-42-301Z.yml98+
A.playwright-cli/page-2026-08-15T22-29-39-961Z.yml9+
A.playwright-cli/page-2026-08-15T22-29-50-869Z.yml72+
A.playwright-cli/page-2026-08-15T22-30-13-730Z.yml72+
A.playwright-cli/page-2026-08-15T22-30-14-918Z.yml72+
A.playwright-cli/page-2026-08-15T22-31-24-786Z.yml15+
A.playwright-cli/page-2026-08-15T22-31-34-028Z.yml18+
A.playwright-cli/page-2026-08-15T22-31-43-553Z.yml124+
A.playwright-cli/page-2026-08-15T22-31-46-231Z.pngbin
A.playwright-cli/page-2026-08-15T22-49-51-089Z.yml9+
A.playwright-cli/page-2026-08-15T22-50-03-361Z.yml72+
A.playwright-cli/page-2026-08-15T22-50-29-170Z.yml72+
A.playwright-cli/page-2026-08-15T22-50-30-326Z.yml72+
AREADME.md19+
Adocs/llms.txt347++
Adocs/scripting.md59+
Amigrations/0001_init.sql77+
Apackage-lock.json6607++++++++++++++++++++++++++++++
Apackage.json24+
Apackages/engine/README.md74+
Apackages/engine/package.json18+
Apackages/engine/src/constants.ts58+
Apackages/engine/src/hash.ts37+
Apackages/engine/src/index.ts8+
Apackages/engine/src/mapgen.ts159+
Apackages/engine/src/rng.ts43+
Apackages/engine/src/world.ts1171+++++
Apackages/engine/test/engine.test.ts352++
Apackages/engine/tsconfig.json9+
Apackages/engine/tsconfig.tsbuildinfo1+
Apackages/protocol/package.json18+
Apackages/protocol/src/api.ts176+
Apackages/protocol/src/game.ts224+
Apackages/protocol/src/index.ts2+
Apackages/protocol/tsconfig.json8+
Apackages/protocol/tsconfig.tsbuildinfo1+
Apackages/script-host/bots/gatherer.js104+
Apackages/script-host/bots/primitives.js150+
Apackages/script-host/bots/rusher.js127+
Apackages/script-host/bots/turtle.js140+
Apackages/script-host/package.json19+
Apackages/script-host/scripts/gen-bots.mjs28+
Apackages/script-host/src/bots.ts533++
Apackages/script-host/src/index.ts489++
Apackages/script-host/src/prelude.ts164+
Apackages/script-host/test/host.test.ts404++
Apackages/script-host/test/primitives.test.ts95+
Apackages/script-host/test/regex-gas.test.ts51+
Apackages/script-host/tsconfig.json9+
Apackages/script-host/tsconfig.tsbuildinfo1+
Ascripts/import-assets.ts160+
Ascripts/release.sh62+
Atsconfig.base.json16+
Aweb/index.html14+
Aweb/package.json19+
Aweb/src/api.ts83+
Aweb/src/auth.ts64+
Aweb/src/dev-mock.ts385++
Aweb/src/dom.ts35+
Aweb/src/log.ts36+
Aweb/src/main.ts108+
Aweb/src/pages/home.ts633+++
Aweb/src/pages/viewer.ts317+
Aweb/src/render/manifest.ts41+
Aweb/src/render/renderer.ts237+
Aweb/src/sim-worker.ts300+
Aweb/src/sim/frames.ts68+
Aweb/src/sim/runner.ts127+
Aweb/src/style.css195+
Aweb/tsconfig.json10+
Aweb/vite.config.ts14+
Aworker/package.json22+
Aworker/src/auth.ts188+
Aworker/src/db.ts487++
Aworker/src/env.ts20+
Aworker/src/examples.ts63+
Aworker/src/index.ts53+
Aworker/src/mcp/docs.ts358++
Aworker/src/mcp/handler.test.ts332++
Aworker/src/mcp/handler.ts399++
Aworker/src/mcp/text-modules.d.ts15+
Aworker/src/mcp/tsconfig.check.json10+
Aworker/src/router.ts314+
Aworker/src/runtime-validation.ts83+
Aworker/src/service.ts349++
Aworker/src/sweep.ts53+
Aworker/src/util.ts53+
Aworker/src/validate.ts75+
Aworker/test/auth.test.ts87+
Aworker/test/sweep.test.ts57+
Aworker/test/util.test.ts55+
Aworker/test/validate.test.ts72+
Aworker/tsconfig.json11+
Awrangler.jsonc86+
A .gitignore +8 -0
A .playwright-cli/console-2026-08-15T22-13-49-782Z.log +1 -0
A .playwright-cli/console-2026-08-15T22-17-57-884Z.log +1 -0
A .playwright-cli/console-2026-08-15T22-20-31-746Z.log +1 -0
A .playwright-cli/console-2026-08-15T22-21-53-096Z.log +12 -0
A .playwright-cli/console-2026-08-15T22-24-44-466Z.log +15 -0
@@ -0,0 +1,15 @@
1+[ 233ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:8787/api/me:02+[ 308ms] [LOG] OUTCOME 0 0 {"gas":0,"ob":false,"err":null,"n":4} @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:1983+[ 309ms] [LOG] OUTCOME 0 1 {"gas":0,"ob":false,"err":null,"n":5} @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:1984+[ 310ms] [LOG] TICKHASH 1 efe151d45fcb7c77 @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:1985+[ 329ms] [LOG] OUTCOME 1 0 {"gas":0,"ob":false,"err":null,"n":2} @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:1986+[ 330ms] [LOG] OUTCOME 1 1 {"gas":0,"ob":false,"err":null,"n":2} @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:1987+[ 354ms] [LOG] OUTCOME 2 0 {"gas":0,"ob":false,"err":null,"n":1} @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:1988+[ 355ms] [LOG] OUTCOME 2 1 {"gas":0,"ob":false,"err":null,"n":1} @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:1989+[ 530ms] [LOG] TICKHASH 10 a5d867e6dc4eabc5 @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:19810+[ 2780ms] [LOG] TICKHASH 100 bb9e82aa15040545 @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:19811+[ 11118ms] [LOG] TICKHASH 1000 f957d93181602076 @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:19812+[ 32091ms] [LOG] TICKHASH 1 15003a4476ca7477 @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:19813+[ 32092ms] [LOG] TICKHASH 10 4d6f1fb749595bc4 @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:19814+[ 32093ms] [LOG] TICKHASH 100 79f2ab0168c72fea @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:19815+[ 32096ms] [LOG] TICKHASH 1000 db22b20c2c789a53 @ http://localhost:8787/assets/sim-worker-7FvNVKTZ.js:198
A .playwright-cli/console-2026-08-15T22-26-13-273Z.log +19 -0
@@ -0,0 +1,19 @@
1+[ 228ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:8787/api/me:02+[ 298ms] [LOG] INIT_DONE 2 2 2 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:1983+[ 299ms] [LOG] VIEWS_AFTER_INIT 2 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:1984+[ 300ms] [LOG] OUTCOME 0 0 {"gas":0,"ob":false,"err":null,"n":4} @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:1985+[ 301ms] [LOG] OUTCOME 0 1 {"gas":0,"ob":false,"err":null,"n":5} @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:1986+[ 302ms] [LOG] TICKHASH 1 efe151d45fcb7c77 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:1987+[ 323ms] [LOG] OUTCOME 1 0 {"gas":0,"ob":false,"err":null,"n":2} @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:1988+[ 323ms] [LOG] OUTCOME 1 1 {"gas":0,"ob":false,"err":null,"n":2} @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:1989+[ 348ms] [LOG] OUTCOME 2 0 {"gas":0,"ob":false,"err":null,"n":1} @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19810+[ 348ms] [LOG] OUTCOME 2 1 {"gas":0,"ob":false,"err":null,"n":1} @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19811+[ 523ms] [LOG] TICKHASH 10 a5d867e6dc4eabc5 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19812+[ 2772ms] [LOG] TICKHASH 100 bb9e82aa15040545 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19813+[ 13340ms] [LOG] TICKHASH 1000 f957d93181602076 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19814+[ 28026ms] [LOG] TICKHASH 1 15003a4476ca7477 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19815+[ 28026ms] [LOG] TICKHASH 10 4d6f1fb749595bc4 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19816+[ 28028ms] [LOG] TICKHASH 100 79f2ab0168c72fea @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19817+[ 28030ms] [LOG] TICKHASH 1000 db22b20c2c789a53 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19818+[ 28056ms] [LOG] INIT_DONE 2 2 2 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:19819+[ 28057ms] [LOG] VIEWS_AFTER_INIT 2 @ http://localhost:8787/assets/sim-worker-CEK660Ti.js:198
A .playwright-cli/console-2026-08-15T22-29-39-637Z.log +29 -0
@@ -0,0 +1,29 @@
1+[ 315ms] [ERROR] Failed to load resource: the server responded with a status of 401 (Unauthorized) @ http://localhost:8787/api/me:02+[ 391ms] [LOG] [tribes:debug] worker init done {tribes: 2, sources: 2} @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:1983+[ 420ms] [LOG] [tribes:debug] state hash 1 efe151d45fcb7c77 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:1984+[ 643ms] [LOG] [tribes:debug] state hash 10 a5d867e6dc4eabc5 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:1985+[ 2893ms] [LOG] [tribes:debug] state hash 100 bb9e82aa15040545 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:1986+[ 10915ms] [LOG] [tribes:debug] state hash 1000 f957d93181602076 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:1987+[ 11407ms] [LOG] [tribes:debug] state hash 2000 f7d1e12d437ba732 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:1988+[ 12045ms] [LOG] [tribes:debug] state hash 3000 aa304f8c891836ff @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:1989+[ 12687ms] [LOG] [tribes:debug] state hash 4000 aaa0a0144cbb6baf @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19810+[ 13329ms] [LOG] [tribes:debug] state hash 5000 62e819c8f3d70353 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19811+[ 13969ms] [LOG] [tribes:debug] state hash 6000 5ecf5fedcee48852 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19812+[ 14612ms] [LOG] [tribes:debug] state hash 7000 fe9bc0dff22032b0 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19813+[ 15257ms] [LOG] [tribes:debug] state hash 8000 665f4fe44728996f @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19814+[ 15898ms] [LOG] [tribes:debug] state hash 9000 0e7bf6bc0616d5df @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19815+[ 16541ms] [LOG] [tribes:debug] state hash 10000 57b8088b040bdea8 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19816+[ 33109ms] [LOG] [tribes:debug] worker init done {tribes: 2, sources: 2} @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19817+[ 33120ms] [LOG] [tribes:debug] state hash 1 efe151d45fcb7c77 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19818+[ 33125ms] [LOG] [tribes:debug] state hash 10 a5d867e6dc4eabc5 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19819+[ 33148ms] [LOG] [tribes:debug] state hash 100 bb9e82aa15040545 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19820+[ 33401ms] [LOG] [tribes:debug] state hash 1000 f957d93181602076 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19821+[ 33892ms] [LOG] [tribes:debug] state hash 2000 f7d1e12d437ba732 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19822+[ 34547ms] [LOG] [tribes:debug] state hash 3000 aa304f8c891836ff @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19823+[ 35189ms] [LOG] [tribes:debug] state hash 4000 aaa0a0144cbb6baf @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19824+[ 35830ms] [LOG] [tribes:debug] state hash 5000 62e819c8f3d70353 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19825+[ 36467ms] [LOG] [tribes:debug] state hash 6000 5ecf5fedcee48852 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19826+[ 37103ms] [LOG] [tribes:debug] state hash 7000 fe9bc0dff22032b0 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19827+[ 37743ms] [LOG] [tribes:debug] state hash 8000 665f4fe44728996f @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19828+[ 38380ms] [LOG] [tribes:debug] state hash 9000 0e7bf6bc0616d5df @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:19829+[ 39019ms] [LOG] [tribes:debug] state hash 10000 57b8088b040bdea8 @ http://localhost:8787/assets/sim-worker-Ce4Nfjxb.js:198
A .playwright-cli/console-2026-08-15T22-31-24-554Z.log +1 -0
A .playwright-cli/console-2026-08-15T22-49-50-836Z.log +1 -0
A .playwright-cli/page-2026-08-15T22-13-50-011Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-14-02-478Z.png +0 -0

Binary file.

A .playwright-cli/page-2026-08-15T22-14-24-319Z.png +0 -0

Binary file.

A .playwright-cli/page-2026-08-15T22-15-05-450Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e18] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [active] [ref=e22] [cursor=pointer]19+ - button "Restart" [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 3750 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "290"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "300"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "160"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "295"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-15-28-885Z.yml +98 -0
@@ -0,0 +1,98 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e104]:12+ - heading "Tie (tick cap reached at tick 10000)" [level=2] [ref=e105]13+ - table [ref=e106]:14+ - rowgroup [ref=e107]:15+ - row [ref=e108]:16+ - columnheader "Tribe" [ref=e109]17+ - columnheader "Score" [ref=e110]18+ - columnheader "Pop" [ref=e111]19+ - columnheader "Kills" [ref=e112]20+ - columnheader "Buildings" [ref=e113]21+ - columnheader "Fate" [ref=e114]22+ - rowgroup [ref=e115]:23+ - row [ref=e116]:24+ - cell "gatherer" [ref=e117]25+ - cell "5" [ref=e119]26+ - cell "0" [ref=e120]27+ - cell "0" [ref=e121]28+ - cell "1" [ref=e122]29+ - cell "survived" [ref=e123]30+ - row [ref=e124]:31+ - cell "rusher" [ref=e125]32+ - cell "5" [ref=e127]33+ - cell "0" [ref=e128]34+ - cell "0" [ref=e129]35+ - cell "1" [ref=e130]36+ - cell "survived" [ref=e131]37+ - generic [ref=e13]:38+ - generic [ref=e14]:39+ - generic [ref=e17]:40+ - button "Play" [disabled] [ref=e102]41+ - button "1×" [ref=e19] [cursor=pointer]42+ - button "4×" [ref=e20] [cursor=pointer]43+ - button "16×" [ref=e21] [cursor=pointer]44+ - button "max" [ref=e22] [cursor=pointer]45+ - button "Restart" [active] [ref=e23] [cursor=pointer]46+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]47+ - combobox [ref=e25]:48+ - 'option "POV: omniscient" [selected]'49+ - 'option "POV: gatherer (slot 0)"'50+ - 'option "POV: rusher (slot 1)"'51+ - generic [ref=e26]: tick 10000 / 1000052+ - paragraph [ref=e103]: final state hash 324a606318f3b6e453+ - generic [ref=e30]:54+ - generic [ref=e31]:55+ - generic [ref=e32]:56+ - generic [ref=e34]: gatherer57+ - generic [ref=e35]: Human Hero58+ - generic "over gas budget" [ref=e36]59+ - generic [ref=e37]: alive60+ - generic [ref=e38]:61+ - generic "food" [ref=e39]:62+ - img "food" [ref=e40]63+ - generic [ref=e41]: "0"64+ - generic "wood" [ref=e42]:65+ - img "wood" [ref=e43]66+ - generic [ref=e44]: "50"67+ - generic "stone" [ref=e45]:68+ - img "stone" [ref=e46]69+ - generic [ref=e47]: "0"70+ - generic "population / cap" [ref=e48]:71+ - text: pop72+ - generic [ref=e49]: 0/1073+ - generic "buildings" [ref=e50]:74+ - text: bld75+ - generic [ref=e51]: "1"76+ - generic [ref=e52]:77+ - generic [ref=e53]:78+ - generic [ref=e55]: rusher79+ - generic [ref=e56]: Human Knight80+ - generic "over gas budget" [ref=e57]81+ - generic [ref=e58]: alive82+ - generic [ref=e59]:83+ - generic "food" [ref=e60]:84+ - img "food" [ref=e61]85+ - generic [ref=e62]: "0"86+ - generic "wood" [ref=e63]:87+ - img "wood" [ref=e64]88+ - generic [ref=e65]: "50"89+ - generic "stone" [ref=e66]:90+ - img "stone" [ref=e67]91+ - generic [ref=e68]: "0"92+ - generic "population / cap" [ref=e69]:93+ - text: pop94+ - generic [ref=e70]: 0/1095+ - generic "buildings" [ref=e71]:96+ - text: bld97+ - generic [ref=e72]: "1"98+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-15-39-139Z.yml +98 -0
@@ -0,0 +1,98 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e104]:12+ - heading "Tie (tick cap reached at tick 10000)" [level=2] [ref=e105]13+ - table [ref=e106]:14+ - rowgroup [ref=e107]:15+ - row [ref=e108]:16+ - columnheader "Tribe" [ref=e109]17+ - columnheader "Score" [ref=e110]18+ - columnheader "Pop" [ref=e111]19+ - columnheader "Kills" [ref=e112]20+ - columnheader "Buildings" [ref=e113]21+ - columnheader "Fate" [ref=e114]22+ - rowgroup [ref=e115]:23+ - row [ref=e116]:24+ - cell "gatherer" [ref=e117]25+ - cell "5" [ref=e119]26+ - cell "0" [ref=e120]27+ - cell "0" [ref=e121]28+ - cell "1" [ref=e122]29+ - cell "survived" [ref=e123]30+ - row [ref=e124]:31+ - cell "rusher" [ref=e125]32+ - cell "5" [ref=e127]33+ - cell "0" [ref=e128]34+ - cell "0" [ref=e129]35+ - cell "1" [ref=e130]36+ - cell "survived" [ref=e131]37+ - generic [ref=e13]:38+ - generic [ref=e14]:39+ - generic [ref=e17]:40+ - button "Play" [disabled] [ref=e102]41+ - button "1×" [ref=e19] [cursor=pointer]42+ - button "4×" [ref=e20] [cursor=pointer]43+ - button "16×" [ref=e21] [cursor=pointer]44+ - button "max" [active] [ref=e22] [cursor=pointer]45+ - button "Restart" [ref=e23] [cursor=pointer]46+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]47+ - combobox [ref=e25]:48+ - 'option "POV: omniscient" [selected]'49+ - 'option "POV: gatherer (slot 0)"'50+ - 'option "POV: rusher (slot 1)"'51+ - generic [ref=e26]: tick 10000 / 1000052+ - paragraph [ref=e103]: final state hash 324a606318f3b6e453+ - generic [ref=e30]:54+ - generic [ref=e31]:55+ - generic [ref=e32]:56+ - generic [ref=e34]: gatherer57+ - generic [ref=e35]: Human Hero58+ - generic "over gas budget" [ref=e36]59+ - generic [ref=e37]: alive60+ - generic [ref=e38]:61+ - generic "food" [ref=e39]:62+ - img "food" [ref=e40]63+ - generic [ref=e41]: "0"64+ - generic "wood" [ref=e42]:65+ - img "wood" [ref=e43]66+ - generic [ref=e44]: "50"67+ - generic "stone" [ref=e45]:68+ - img "stone" [ref=e46]69+ - generic [ref=e47]: "0"70+ - generic "population / cap" [ref=e48]:71+ - text: pop72+ - generic [ref=e49]: 0/1073+ - generic "buildings" [ref=e50]:74+ - text: bld75+ - generic [ref=e51]: "1"76+ - generic [ref=e52]:77+ - generic [ref=e53]:78+ - generic [ref=e55]: rusher79+ - generic [ref=e56]: Human Knight80+ - generic "over gas budget" [ref=e57]81+ - generic [ref=e58]: alive82+ - generic [ref=e59]:83+ - generic "food" [ref=e60]:84+ - img "food" [ref=e61]85+ - generic [ref=e62]: "0"86+ - generic "wood" [ref=e63]:87+ - img "wood" [ref=e64]88+ - generic [ref=e65]: "50"89+ - generic "stone" [ref=e66]:90+ - img "stone" [ref=e67]91+ - generic [ref=e68]: "0"92+ - generic "population / cap" [ref=e69]:93+ - text: pop94+ - generic [ref=e70]: 0/1095+ - generic "buildings" [ref=e71]:96+ - text: bld97+ - generic [ref=e72]: "1"98+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-17-58-166Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-18-07-896Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e18] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [active] [ref=e22] [cursor=pointer]19+ - button "Restart" [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 1675 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "300"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "184"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "158"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "300"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-18-30-653Z.yml +98 -0
@@ -0,0 +1,98 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e104]:12+ - heading "Tie (tick cap reached at tick 10000)" [level=2] [ref=e105]13+ - table [ref=e106]:14+ - rowgroup [ref=e107]:15+ - row [ref=e108]:16+ - columnheader "Tribe" [ref=e109]17+ - columnheader "Score" [ref=e110]18+ - columnheader "Pop" [ref=e111]19+ - columnheader "Kills" [ref=e112]20+ - columnheader "Buildings" [ref=e113]21+ - columnheader "Fate" [ref=e114]22+ - rowgroup [ref=e115]:23+ - row [ref=e116]:24+ - cell "gatherer" [ref=e117]25+ - cell "5" [ref=e119]26+ - cell "0" [ref=e120]27+ - cell "0" [ref=e121]28+ - cell "1" [ref=e122]29+ - cell "survived" [ref=e123]30+ - row [ref=e124]:31+ - cell "rusher" [ref=e125]32+ - cell "5" [ref=e127]33+ - cell "0" [ref=e128]34+ - cell "0" [ref=e129]35+ - cell "1" [ref=e130]36+ - cell "survived" [ref=e131]37+ - generic [ref=e13]:38+ - generic [ref=e14]:39+ - generic [ref=e17]:40+ - button "Play" [disabled] [ref=e102]41+ - button "1×" [ref=e19] [cursor=pointer]42+ - button "4×" [ref=e20] [cursor=pointer]43+ - button "16×" [ref=e21] [cursor=pointer]44+ - button "max" [ref=e22] [cursor=pointer]45+ - button "Restart" [active] [ref=e23] [cursor=pointer]46+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]47+ - combobox [ref=e25]:48+ - 'option "POV: omniscient" [selected]'49+ - 'option "POV: gatherer (slot 0)"'50+ - 'option "POV: rusher (slot 1)"'51+ - generic [ref=e26]: tick 10000 / 1000052+ - paragraph [ref=e103]: final state hash 324a606318f3b6e453+ - generic [ref=e30]:54+ - generic [ref=e31]:55+ - generic [ref=e32]:56+ - generic [ref=e34]: gatherer57+ - generic [ref=e35]: Human Hero58+ - generic "over gas budget" [ref=e36]59+ - generic [ref=e37]: alive60+ - generic [ref=e38]:61+ - generic "food" [ref=e39]:62+ - img "food" [ref=e40]63+ - generic [ref=e41]: "0"64+ - generic "wood" [ref=e42]:65+ - img "wood" [ref=e43]66+ - generic [ref=e44]: "50"67+ - generic "stone" [ref=e45]:68+ - img "stone" [ref=e46]69+ - generic [ref=e47]: "0"70+ - generic "population / cap" [ref=e48]:71+ - text: pop72+ - generic [ref=e49]: 0/1073+ - generic "buildings" [ref=e50]:74+ - text: bld75+ - generic [ref=e51]: "1"76+ - generic [ref=e52]:77+ - generic [ref=e53]:78+ - generic [ref=e55]: rusher79+ - generic [ref=e56]: Human Knight80+ - generic "over gas budget" [ref=e57]81+ - generic [ref=e58]: alive82+ - generic [ref=e59]:83+ - generic "food" [ref=e60]:84+ - img "food" [ref=e61]85+ - generic [ref=e62]: "0"86+ - generic "wood" [ref=e63]:87+ - img "wood" [ref=e64]88+ - generic [ref=e65]: "50"89+ - generic "stone" [ref=e66]:90+ - img "stone" [ref=e67]91+ - generic [ref=e68]: "0"92+ - generic "population / cap" [ref=e69]:93+ - text: pop94+ - generic [ref=e70]: 0/1095+ - generic "buildings" [ref=e71]:96+ - text: bld97+ - generic [ref=e72]: "1"98+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-18-32-845Z.yml +98 -0
@@ -0,0 +1,98 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e104]:12+ - heading "Tie (tick cap reached at tick 10000)" [level=2] [ref=e105]13+ - table [ref=e106]:14+ - rowgroup [ref=e107]:15+ - row [ref=e108]:16+ - columnheader "Tribe" [ref=e109]17+ - columnheader "Score" [ref=e110]18+ - columnheader "Pop" [ref=e111]19+ - columnheader "Kills" [ref=e112]20+ - columnheader "Buildings" [ref=e113]21+ - columnheader "Fate" [ref=e114]22+ - rowgroup [ref=e115]:23+ - row [ref=e116]:24+ - cell "gatherer" [ref=e117]25+ - cell "5" [ref=e119]26+ - cell "0" [ref=e120]27+ - cell "0" [ref=e121]28+ - cell "1" [ref=e122]29+ - cell "survived" [ref=e123]30+ - row [ref=e124]:31+ - cell "rusher" [ref=e125]32+ - cell "5" [ref=e127]33+ - cell "0" [ref=e128]34+ - cell "0" [ref=e129]35+ - cell "1" [ref=e130]36+ - cell "survived" [ref=e131]37+ - generic [ref=e13]:38+ - generic [ref=e14]:39+ - generic [ref=e17]:40+ - button "Play" [disabled] [ref=e102]41+ - button "1×" [ref=e19] [cursor=pointer]42+ - button "4×" [ref=e20] [cursor=pointer]43+ - button "16×" [ref=e21] [cursor=pointer]44+ - button "max" [active] [ref=e22] [cursor=pointer]45+ - button "Restart" [ref=e23] [cursor=pointer]46+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]47+ - combobox [ref=e25]:48+ - 'option "POV: omniscient" [selected]'49+ - 'option "POV: gatherer (slot 0)"'50+ - 'option "POV: rusher (slot 1)"'51+ - generic [ref=e26]: tick 10000 / 1000052+ - paragraph [ref=e103]: final state hash 324a606318f3b6e453+ - generic [ref=e30]:54+ - generic [ref=e31]:55+ - generic [ref=e32]:56+ - generic [ref=e34]: gatherer57+ - generic [ref=e35]: Human Hero58+ - generic "over gas budget" [ref=e36]59+ - generic [ref=e37]: alive60+ - generic [ref=e38]:61+ - generic "food" [ref=e39]:62+ - img "food" [ref=e40]63+ - generic [ref=e41]: "0"64+ - generic "wood" [ref=e42]:65+ - img "wood" [ref=e43]66+ - generic [ref=e44]: "50"67+ - generic "stone" [ref=e45]:68+ - img "stone" [ref=e46]69+ - generic [ref=e47]: "0"70+ - generic "population / cap" [ref=e48]:71+ - text: pop72+ - generic [ref=e49]: 0/1073+ - generic "buildings" [ref=e50]:74+ - text: bld75+ - generic [ref=e51]: "1"76+ - generic [ref=e52]:77+ - generic [ref=e53]:78+ - generic [ref=e55]: rusher79+ - generic [ref=e56]: Human Knight80+ - generic "over gas budget" [ref=e57]81+ - generic [ref=e58]: alive82+ - generic [ref=e59]:83+ - generic "food" [ref=e60]:84+ - img "food" [ref=e61]85+ - generic [ref=e62]: "0"86+ - generic "wood" [ref=e63]:87+ - img "wood" [ref=e64]88+ - generic [ref=e65]: "50"89+ - generic "stone" [ref=e66]:90+ - img "stone" [ref=e67]91+ - generic [ref=e68]: "0"92+ - generic "population / cap" [ref=e69]:93+ - text: pop94+ - generic [ref=e70]: 0/1095+ - generic "buildings" [ref=e71]:96+ - text: bld97+ - generic [ref=e72]: "1"98+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-20-32-055Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-21-09-352Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e18] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [active] [ref=e22] [cursor=pointer]19+ - button "Restart" [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 2250 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "290"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "300"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "159"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "295"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-21-53-393Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-22-06-251Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e18]:12+ - generic [ref=e19]:13+ - generic [ref=e22]:14+ - button "Pause" [ref=e23] [cursor=pointer]15+ - button "1×" [ref=e24] [cursor=pointer]16+ - button "4×" [ref=e25] [cursor=pointer]17+ - button "16×" [ref=e26] [cursor=pointer]18+ - button "max" [ref=e27] [cursor=pointer]19+ - button "Restart" [active] [ref=e28] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]21+ - combobox [ref=e30]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e31]: tick 39 / 1000026+ - paragraph27+ - generic [ref=e35]:28+ - generic [ref=e36]:29+ - generic [ref=e37]:30+ - generic [ref=e39]: gatherer31+ - generic [ref=e40]: Human Hero32+ - generic "over gas budget" [ref=e41]33+ - generic [ref=e42]: alive34+ - generic [ref=e43]:35+ - generic "food" [ref=e44]:36+ - img "food" [ref=e45]37+ - generic [ref=e46]: "32"38+ - generic "wood" [ref=e47]:39+ - img "wood" [ref=e48]40+ - generic [ref=e49]: "7"41+ - generic "stone" [ref=e50]:42+ - img "stone" [ref=e51]43+ - generic [ref=e52]: "30"44+ - generic "population / cap" [ref=e53]:45+ - text: pop46+ - generic [ref=e54]: 10/1047+ - generic "buildings" [ref=e55]:48+ - text: bld49+ - generic [ref=e56]: "5"50+ - generic [ref=e57]:51+ - generic [ref=e58]:52+ - generic [ref=e60]: rusher53+ - generic [ref=e61]: Human Knight54+ - generic "over gas budget" [ref=e62]55+ - generic [ref=e63]: alive56+ - generic [ref=e64]:57+ - generic "food" [ref=e65]:58+ - img "food" [ref=e66]59+ - generic [ref=e67]: "185"60+ - generic "wood" [ref=e68]:61+ - img "wood" [ref=e69]62+ - generic [ref=e70]: "37"63+ - generic "stone" [ref=e71]:64+ - img "stone" [ref=e72]65+ - generic [ref=e73]: "0"66+ - generic "population / cap" [ref=e74]:67+ - text: pop68+ - generic [ref=e75]: 5/1069+ - generic "buildings" [ref=e76]:70+ - text: bld71+ - generic [ref=e77]: "2"72+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-23-02-712Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e18]:12+ - generic [ref=e19]:13+ - generic [ref=e22]:14+ - button "Pause" [ref=e23] [cursor=pointer]15+ - button "1×" [ref=e24] [cursor=pointer]16+ - button "4×" [ref=e25] [cursor=pointer]17+ - button "16×" [ref=e26] [cursor=pointer]18+ - button "max" [ref=e27] [cursor=pointer]19+ - button "Restart" [ref=e28] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]21+ - combobox [active] [ref=e30]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e31]: tick 2298 / 1000026+ - paragraph27+ - generic [ref=e35]:28+ - generic [ref=e36]:29+ - generic [ref=e37]:30+ - generic [ref=e39]: gatherer31+ - generic [ref=e40]: Human Hero32+ - generic "over gas budget" [ref=e41]33+ - generic [ref=e42]: alive34+ - generic [ref=e43]:35+ - generic "food" [ref=e44]:36+ - img "food" [ref=e45]37+ - generic [ref=e46]: "300"38+ - generic "wood" [ref=e47]:39+ - img "wood" [ref=e48]40+ - generic [ref=e49]: "300"41+ - generic "stone" [ref=e50]:42+ - img "stone" [ref=e51]43+ - generic [ref=e52]: "300"44+ - generic "population / cap" [ref=e53]:45+ - text: pop46+ - generic [ref=e54]: 10/1047+ - generic "buildings" [ref=e55]:48+ - text: bld49+ - generic [ref=e56]: "159"50+ - generic [ref=e57]:51+ - generic [ref=e58]:52+ - generic [ref=e60]: rusher53+ - generic [ref=e61]: Human Knight54+ - generic "over gas budget" [ref=e62]55+ - generic [ref=e63]: alive56+ - generic [ref=e64]:57+ - generic "food" [ref=e65]:58+ - img "food" [ref=e66]59+ - generic [ref=e67]: "300"60+ - generic "wood" [ref=e68]:61+ - img "wood" [ref=e69]62+ - generic [ref=e70]: "300"63+ - generic "stone" [ref=e71]:64+ - img "stone" [ref=e72]65+ - generic [ref=e73]: "0"66+ - generic "population / cap" [ref=e74]:67+ - text: pop68+ - generic [ref=e75]: 5/1069+ - generic "buildings" [ref=e76]:70+ - text: bld71+ - generic [ref=e77]: "2"72+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-23-22-458Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e18]:12+ - generic [ref=e19]:13+ - generic [ref=e22]:14+ - button "Pause" [ref=e23] [cursor=pointer]15+ - button "1×" [ref=e24] [cursor=pointer]16+ - button "4×" [ref=e25] [cursor=pointer]17+ - button "16×" [ref=e26] [cursor=pointer]18+ - button "max" [active] [ref=e27] [cursor=pointer]19+ - button "Restart" [ref=e28] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]21+ - combobox [ref=e30]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e31]: tick 3800 / 1000026+ - paragraph27+ - generic [ref=e35]:28+ - generic [ref=e36]:29+ - generic [ref=e37]:30+ - generic [ref=e39]: gatherer31+ - generic [ref=e40]: Human Hero32+ - generic "over gas budget" [ref=e41]33+ - generic [ref=e42]: alive34+ - generic [ref=e43]:35+ - generic "food" [ref=e44]:36+ - img "food" [ref=e45]37+ - generic [ref=e46]: "290"38+ - generic "wood" [ref=e47]:39+ - img "wood" [ref=e48]40+ - generic [ref=e49]: "300"41+ - generic "stone" [ref=e50]:42+ - img "stone" [ref=e51]43+ - generic [ref=e52]: "300"44+ - generic "population / cap" [ref=e53]:45+ - text: pop46+ - generic [ref=e54]: 10/1047+ - generic "buildings" [ref=e55]:48+ - text: bld49+ - generic [ref=e56]: "160"50+ - generic [ref=e57]:51+ - generic [ref=e58]:52+ - generic [ref=e60]: rusher53+ - generic [ref=e61]: Human Knight54+ - generic "over gas budget" [ref=e62]55+ - generic [ref=e63]: alive56+ - generic [ref=e64]:57+ - generic "food" [ref=e65]:58+ - img "food" [ref=e66]59+ - generic [ref=e67]: "295"60+ - generic "wood" [ref=e68]:61+ - img "wood" [ref=e69]62+ - generic [ref=e70]: "300"63+ - generic "stone" [ref=e71]:64+ - img "stone" [ref=e72]65+ - generic [ref=e73]: "0"66+ - generic "population / cap" [ref=e74]:67+ - text: pop68+ - generic [ref=e75]: 5/1069+ - generic "buildings" [ref=e76]:70+ - text: bld71+ - generic [ref=e77]: "2"72+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-23-51-018Z.yml +98 -0
@@ -0,0 +1,98 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e110]:12+ - heading "Tie (tick cap reached at tick 10000)" [level=2] [ref=e111]13+ - table [ref=e112]:14+ - rowgroup [ref=e113]:15+ - row [ref=e114]:16+ - columnheader "Tribe" [ref=e115]17+ - columnheader "Score" [ref=e116]18+ - columnheader "Pop" [ref=e117]19+ - columnheader "Kills" [ref=e118]20+ - columnheader "Buildings" [ref=e119]21+ - columnheader "Fate" [ref=e120]22+ - rowgroup [ref=e121]:23+ - row [ref=e122]:24+ - cell "gatherer" [ref=e123]25+ - cell "5" [ref=e125]26+ - cell "0" [ref=e126]27+ - cell "0" [ref=e127]28+ - cell "1" [ref=e128]29+ - cell "survived" [ref=e129]30+ - row [ref=e130]:31+ - cell "rusher" [ref=e131]32+ - cell "5" [ref=e133]33+ - cell "0" [ref=e134]34+ - cell "0" [ref=e135]35+ - cell "1" [ref=e136]36+ - cell "survived" [ref=e137]37+ - generic [ref=e18]:38+ - generic [ref=e19]:39+ - generic [ref=e22]:40+ - button "Play" [disabled] [ref=e108]41+ - button "1×" [ref=e24] [cursor=pointer]42+ - button "4×" [ref=e25] [cursor=pointer]43+ - button "16×" [ref=e26] [cursor=pointer]44+ - button "max" [ref=e27] [cursor=pointer]45+ - button "Restart" [active] [ref=e28] [cursor=pointer]46+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]47+ - combobox [ref=e30]:48+ - 'option "POV: omniscient" [selected]'49+ - 'option "POV: gatherer (slot 0)"'50+ - 'option "POV: rusher (slot 1)"'51+ - generic [ref=e31]: tick 10000 / 1000052+ - paragraph [ref=e109]: final state hash 324a606318f3b6e453+ - generic [ref=e35]:54+ - generic [ref=e36]:55+ - generic [ref=e37]:56+ - generic [ref=e39]: gatherer57+ - generic [ref=e40]: Human Hero58+ - generic "over gas budget" [ref=e41]59+ - generic [ref=e42]: alive60+ - generic [ref=e43]:61+ - generic "food" [ref=e44]:62+ - img "food" [ref=e45]63+ - generic [ref=e46]: "0"64+ - generic "wood" [ref=e47]:65+ - img "wood" [ref=e48]66+ - generic [ref=e49]: "50"67+ - generic "stone" [ref=e50]:68+ - img "stone" [ref=e51]69+ - generic [ref=e52]: "0"70+ - generic "population / cap" [ref=e53]:71+ - text: pop72+ - generic [ref=e54]: 0/1073+ - generic "buildings" [ref=e55]:74+ - text: bld75+ - generic [ref=e56]: "1"76+ - generic [ref=e57]:77+ - generic [ref=e58]:78+ - generic [ref=e60]: rusher79+ - generic [ref=e61]: Human Knight80+ - generic "over gas budget" [ref=e62]81+ - generic [ref=e63]: alive82+ - generic [ref=e64]:83+ - generic "food" [ref=e65]:84+ - img "food" [ref=e66]85+ - generic [ref=e67]: "0"86+ - generic "wood" [ref=e68]:87+ - img "wood" [ref=e69]88+ - generic [ref=e70]: "50"89+ - generic "stone" [ref=e71]:90+ - img "stone" [ref=e72]91+ - generic [ref=e73]: "0"92+ - generic "population / cap" [ref=e74]:93+ - text: pop94+ - generic [ref=e75]: 0/1095+ - generic "buildings" [ref=e76]:96+ - text: bld97+ - generic [ref=e77]: "1"98+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-24-44-702Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-24-55-903Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e18]:12+ - generic [ref=e19]:13+ - generic [ref=e22]:14+ - button "Pause" [ref=e23] [cursor=pointer]15+ - button "1×" [ref=e24] [cursor=pointer]16+ - button "4×" [ref=e25] [cursor=pointer]17+ - button "16×" [ref=e26] [cursor=pointer]18+ - button "max" [active] [ref=e27] [cursor=pointer]19+ - button "Restart" [ref=e28] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]21+ - combobox [ref=e30]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e31]: tick 1700 / 1000026+ - paragraph27+ - generic [ref=e35]:28+ - generic [ref=e36]:29+ - generic [ref=e37]:30+ - generic [ref=e39]: gatherer31+ - generic [ref=e40]: Human Hero32+ - generic "over gas budget" [ref=e41]33+ - generic [ref=e42]: alive34+ - generic [ref=e43]:35+ - generic "food" [ref=e44]:36+ - img "food" [ref=e45]37+ - generic [ref=e46]: "290"38+ - generic "wood" [ref=e47]:39+ - img "wood" [ref=e48]40+ - generic [ref=e49]: "259"41+ - generic "stone" [ref=e50]:42+ - img "stone" [ref=e51]43+ - generic [ref=e52]: "300"44+ - generic "population / cap" [ref=e53]:45+ - text: pop46+ - generic [ref=e54]: 10/1047+ - generic "buildings" [ref=e55]:48+ - text: bld49+ - generic [ref=e56]: "158"50+ - generic [ref=e57]:51+ - generic [ref=e58]:52+ - generic [ref=e60]: rusher53+ - generic [ref=e61]: Human Knight54+ - generic "over gas budget" [ref=e62]55+ - generic [ref=e63]: alive56+ - generic [ref=e64]:57+ - generic "food" [ref=e65]:58+ - img "food" [ref=e66]59+ - generic [ref=e67]: "295"60+ - generic "wood" [ref=e68]:61+ - img "wood" [ref=e69]62+ - generic [ref=e70]: "300"63+ - generic "stone" [ref=e71]:64+ - img "stone" [ref=e72]65+ - generic [ref=e73]: "0"66+ - generic "population / cap" [ref=e74]:67+ - text: pop68+ - generic [ref=e75]: 5/1069+ - generic "buildings" [ref=e76]:70+ - text: bld71+ - generic [ref=e77]: "2"72+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-25-17-559Z.yml +98 -0
@@ -0,0 +1,98 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e110]:12+ - heading "Tie (tick cap reached at tick 10000)" [level=2] [ref=e111]13+ - table [ref=e112]:14+ - rowgroup [ref=e113]:15+ - row [ref=e114]:16+ - columnheader "Tribe" [ref=e115]17+ - columnheader "Score" [ref=e116]18+ - columnheader "Pop" [ref=e117]19+ - columnheader "Kills" [ref=e118]20+ - columnheader "Buildings" [ref=e119]21+ - columnheader "Fate" [ref=e120]22+ - rowgroup [ref=e121]:23+ - row [ref=e122]:24+ - cell "gatherer" [ref=e123]25+ - cell "5" [ref=e125]26+ - cell "0" [ref=e126]27+ - cell "0" [ref=e127]28+ - cell "1" [ref=e128]29+ - cell "survived" [ref=e129]30+ - row [ref=e130]:31+ - cell "rusher" [ref=e131]32+ - cell "5" [ref=e133]33+ - cell "0" [ref=e134]34+ - cell "0" [ref=e135]35+ - cell "1" [ref=e136]36+ - cell "survived" [ref=e137]37+ - generic [ref=e18]:38+ - generic [ref=e19]:39+ - generic [ref=e22]:40+ - button "Play" [disabled] [ref=e108]41+ - button "1×" [ref=e24] [cursor=pointer]42+ - button "4×" [ref=e25] [cursor=pointer]43+ - button "16×" [ref=e26] [cursor=pointer]44+ - button "max" [ref=e27] [cursor=pointer]45+ - button "Restart" [active] [ref=e28] [cursor=pointer]46+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]47+ - combobox [ref=e30]:48+ - 'option "POV: omniscient" [selected]'49+ - 'option "POV: gatherer (slot 0)"'50+ - 'option "POV: rusher (slot 1)"'51+ - generic [ref=e31]: tick 10000 / 1000052+ - paragraph [ref=e109]: final state hash 324a606318f3b6e453+ - generic [ref=e35]:54+ - generic [ref=e36]:55+ - generic [ref=e37]:56+ - generic [ref=e39]: gatherer57+ - generic [ref=e40]: Human Hero58+ - generic "over gas budget" [ref=e41]59+ - generic [ref=e42]: alive60+ - generic [ref=e43]:61+ - generic "food" [ref=e44]:62+ - img "food" [ref=e45]63+ - generic [ref=e46]: "0"64+ - generic "wood" [ref=e47]:65+ - img "wood" [ref=e48]66+ - generic [ref=e49]: "50"67+ - generic "stone" [ref=e50]:68+ - img "stone" [ref=e51]69+ - generic [ref=e52]: "0"70+ - generic "population / cap" [ref=e53]:71+ - text: pop72+ - generic [ref=e54]: 0/1073+ - generic "buildings" [ref=e55]:74+ - text: bld75+ - generic [ref=e56]: "1"76+ - generic [ref=e57]:77+ - generic [ref=e58]:78+ - generic [ref=e60]: rusher79+ - generic [ref=e61]: Human Knight80+ - generic "over gas budget" [ref=e62]81+ - generic [ref=e63]: alive82+ - generic [ref=e64]:83+ - generic "food" [ref=e65]:84+ - img "food" [ref=e66]85+ - generic [ref=e67]: "0"86+ - generic "wood" [ref=e68]:87+ - img "wood" [ref=e69]88+ - generic [ref=e70]: "50"89+ - generic "stone" [ref=e71]:90+ - img "stone" [ref=e72]91+ - generic [ref=e73]: "0"92+ - generic "population / cap" [ref=e74]:93+ - text: pop94+ - generic [ref=e75]: 0/1095+ - generic "buildings" [ref=e76]:96+ - text: bld97+ - generic [ref=e77]: "1"98+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-26-13-505Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-26-26-954Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e18]:12+ - generic [ref=e19]:13+ - generic [ref=e22]:14+ - button "Pause" [ref=e23] [cursor=pointer]15+ - button "1×" [ref=e24] [cursor=pointer]16+ - button "4×" [ref=e25] [cursor=pointer]17+ - button "16×" [ref=e26] [cursor=pointer]18+ - button "max" [active] [ref=e27] [cursor=pointer]19+ - button "Restart" [ref=e28] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]21+ - combobox [ref=e30]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e31]: tick 1750 / 1000026+ - paragraph27+ - generic [ref=e35]:28+ - generic [ref=e36]:29+ - generic [ref=e37]:30+ - generic [ref=e39]: gatherer31+ - generic [ref=e40]: Human Hero32+ - generic "over gas budget" [ref=e41]33+ - generic [ref=e42]: alive34+ - generic [ref=e43]:35+ - generic "food" [ref=e44]:36+ - img "food" [ref=e45]37+ - generic [ref=e46]: "290"38+ - generic "wood" [ref=e47]:39+ - img "wood" [ref=e48]40+ - generic [ref=e49]: "300"41+ - generic "stone" [ref=e50]:42+ - img "stone" [ref=e51]43+ - generic [ref=e52]: "300"44+ - generic "population / cap" [ref=e53]:45+ - text: pop46+ - generic [ref=e54]: 10/1047+ - generic "buildings" [ref=e55]:48+ - text: bld49+ - generic [ref=e56]: "159"50+ - generic [ref=e57]:51+ - generic [ref=e58]:52+ - generic [ref=e60]: rusher53+ - generic [ref=e61]: Human Knight54+ - generic "over gas budget" [ref=e62]55+ - generic [ref=e63]: alive56+ - generic [ref=e64]:57+ - generic "food" [ref=e65]:58+ - img "food" [ref=e66]59+ - generic [ref=e67]: "295"60+ - generic "wood" [ref=e68]:61+ - img "wood" [ref=e69]62+ - generic [ref=e70]: "300"63+ - generic "stone" [ref=e71]:64+ - img "stone" [ref=e72]65+ - generic [ref=e73]: "0"66+ - generic "population / cap" [ref=e74]:67+ - text: pop68+ - generic [ref=e75]: 5/1069+ - generic "buildings" [ref=e76]:70+ - text: bld71+ - generic [ref=e77]: "2"72+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-26-42-301Z.yml +98 -0
@@ -0,0 +1,98 @@
1+- generic [ref=e2]:2+ - generic [ref=e10]:3+ - link "TRIBES" [ref=e11] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e12]: strategy-script fishbowl6+ - generic [ref=e13]: not signed in7+ - main [ref=e14]:8+ - generic [ref=e15]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e16]10+ - generic [ref=e17]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e110]:12+ - heading "Tie (tick cap reached at tick 10000)" [level=2] [ref=e111]13+ - table [ref=e112]:14+ - rowgroup [ref=e113]:15+ - row [ref=e114]:16+ - columnheader "Tribe" [ref=e115]17+ - columnheader "Score" [ref=e116]18+ - columnheader "Pop" [ref=e117]19+ - columnheader "Kills" [ref=e118]20+ - columnheader "Buildings" [ref=e119]21+ - columnheader "Fate" [ref=e120]22+ - rowgroup [ref=e121]:23+ - row [ref=e122]:24+ - cell "gatherer" [ref=e123]25+ - cell "5" [ref=e125]26+ - cell "0" [ref=e126]27+ - cell "0" [ref=e127]28+ - cell "1" [ref=e128]29+ - cell "survived" [ref=e129]30+ - row [ref=e130]:31+ - cell "rusher" [ref=e131]32+ - cell "5" [ref=e133]33+ - cell "0" [ref=e134]34+ - cell "0" [ref=e135]35+ - cell "1" [ref=e136]36+ - cell "survived" [ref=e137]37+ - generic [ref=e18]:38+ - generic [ref=e19]:39+ - generic [ref=e22]:40+ - button "Play" [disabled] [ref=e108]41+ - button "1×" [ref=e24] [cursor=pointer]42+ - button "4×" [ref=e25] [cursor=pointer]43+ - button "16×" [ref=e26] [cursor=pointer]44+ - button "max" [ref=e27] [cursor=pointer]45+ - button "Restart" [active] [ref=e28] [cursor=pointer]46+ - button "Zoom 1:1" [ref=e29] [cursor=pointer]47+ - combobox [ref=e30]:48+ - 'option "POV: omniscient" [selected]'49+ - 'option "POV: gatherer (slot 0)"'50+ - 'option "POV: rusher (slot 1)"'51+ - generic [ref=e31]: tick 10000 / 1000052+ - paragraph [ref=e109]: final state hash 324a606318f3b6e453+ - generic [ref=e35]:54+ - generic [ref=e36]:55+ - generic [ref=e37]:56+ - generic [ref=e39]: gatherer57+ - generic [ref=e40]: Human Hero58+ - generic "over gas budget" [ref=e41]59+ - generic [ref=e42]: alive60+ - generic [ref=e43]:61+ - generic "food" [ref=e44]:62+ - img "food" [ref=e45]63+ - generic [ref=e46]: "0"64+ - generic "wood" [ref=e47]:65+ - img "wood" [ref=e48]66+ - generic [ref=e49]: "50"67+ - generic "stone" [ref=e50]:68+ - img "stone" [ref=e51]69+ - generic [ref=e52]: "0"70+ - generic "population / cap" [ref=e53]:71+ - text: pop72+ - generic [ref=e54]: 0/1073+ - generic "buildings" [ref=e55]:74+ - text: bld75+ - generic [ref=e56]: "1"76+ - generic [ref=e57]:77+ - generic [ref=e58]:78+ - generic [ref=e60]: rusher79+ - generic [ref=e61]: Human Knight80+ - generic "over gas budget" [ref=e62]81+ - generic [ref=e63]: alive82+ - generic [ref=e64]:83+ - generic "food" [ref=e65]:84+ - img "food" [ref=e66]85+ - generic [ref=e67]: "0"86+ - generic "wood" [ref=e68]:87+ - img "wood" [ref=e69]88+ - generic [ref=e70]: "50"89+ - generic "stone" [ref=e71]:90+ - img "stone" [ref=e72]91+ - generic [ref=e73]: "0"92+ - generic "population / cap" [ref=e74]:93+ - text: pop94+ - generic [ref=e75]: 0/1095+ - generic "buildings" [ref=e76]:96+ - text: bld97+ - generic [ref=e77]: "1"98+ - contentinfo [ref=e78]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-29-39-961Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-29-50-869Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e18] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [active] [ref=e22] [cursor=pointer]19+ - button "Restart" [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 1700 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "290"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "259"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "158"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "295"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-30-13-730Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e104] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [ref=e22] [cursor=pointer]19+ - button "Restart" [active] [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 2275 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "300"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "300"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "159"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "300"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-30-14-918Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e104] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [active] [ref=e22] [cursor=pointer]19+ - button "Restart" [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 4125 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "300"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "300"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "160"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "300"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-31-24-786Z.yml +15 -0
A .playwright-cli/page-2026-08-15T22-31-34-028Z.yml +18 -0
A .playwright-cli/page-2026-08-15T22-31-43-553Z.yml +124 -0
@@ -0,0 +1,124 @@
1+- generic [ref=e2]:2+ - generic [ref=e19]:3+ - link "TRIBES" [ref=e20] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e21]: strategy-script fishbowl6+ - generic [ref=e22]:7+ - generic [ref=e23]: local:test8+ - button "Sign out" [ref=e24] [cursor=pointer]9+ - main [ref=e25]:10+ - generic [ref=e26]:11+ - heading "Scripts" [level=2] [ref=e27]12+ - combobox [ref=e29]:13+ - option "3 scripts — select to inspect" [selected]14+ - option "turtle-mcp v1 [validated]"15+ - option "rusher v1 [validated]"16+ - option "gatherer v1 [validated]"17+ - table [ref=e30]:18+ - rowgroup [ref=e31]:19+ - row [ref=e32]:20+ - columnheader [ref=e33]21+ - columnheader "Name" [ref=e34]22+ - columnheader "Version" [ref=e35]23+ - columnheader "Hash" [ref=e36]24+ - columnheader "Status" [ref=e37]25+ - columnheader "Expiry" [ref=e38]26+ - rowgroup [ref=e39]:27+ - row [ref=e40] [cursor=pointer]:28+ - cell [ref=e41]:29+ - button "☆" [ref=e42]30+ - cell "turtle-mcp" [ref=e43]31+ - cell "v1" [ref=e44]32+ - cell "c863d96df6" [ref=e45]33+ - cell "validated" [ref=e46]34+ - cell "expires Aug 29" [ref=e47]35+ - row [ref=e48] [cursor=pointer]:36+ - cell [ref=e49]:37+ - button "☆" [ref=e50]38+ - cell "rusher" [ref=e51]39+ - cell "v1" [ref=e52]40+ - cell "ad1dcc95d8" [ref=e53]41+ - cell "validated" [ref=e54]42+ - cell "expires Aug 29" [ref=e55]43+ - row [ref=e56] [cursor=pointer]:44+ - cell [ref=e57]:45+ - button "☆" [ref=e58]46+ - cell "gatherer" [ref=e59]47+ - cell "v1" [ref=e60]48+ - cell "cc4bec84eb" [ref=e61]49+ - cell "validated" [ref=e62]50+ - cell "expires Aug 29" [ref=e63]51+ - heading "New script" [level=3] [ref=e64]52+ - textbox "script name" [ref=e66]53+ - 'textbox "// Tribe script: define a tick function. // function tick(state, api) { return { orders: [...] } } // See /llms.txt for the full scripting reference." [ref=e67]':54+ - /placeholder: "// Tribe script: define a tick function.\n// function tick(state, api) { return { orders: [...] } }\n// See /llms.txt for the full scripting reference."55+ - button "Submit script" [ref=e69] [cursor=pointer]56+ - generic [ref=e70]:57+ - heading "New sim" [level=2] [ref=e71]58+ - generic [ref=e72]:59+ - combobox [ref=e73]:60+ - option "2 tribes" [selected]61+ - option "3 tribes"62+ - option "4 tribes"63+ - option "5 tribes"64+ - option "6 tribes"65+ - option "7 tribes"66+ - option "8 tribes"67+ - textbox "seed (blank = random)" [ref=e74]68+ - generic [ref=e75]:69+ - generic [ref=e76]:70+ - generic [ref=e77]: slot 071+ - combobox [ref=e78]:72+ - option "turtle-mcp v1" [selected]73+ - option "rusher v1"74+ - option "gatherer v1"75+ - generic [ref=e79]:76+ - generic [ref=e80]: slot 177+ - combobox [ref=e81]:78+ - option "turtle-mcp v1"79+ - option "rusher v1" [selected]80+ - option "gatherer v1"81+ - button "Create sim" [ref=e83] [cursor=pointer]82+ - generic [ref=e84]:83+ - heading "Sims" [level=2] [ref=e85]84+ - table [ref=e86]:85+ - rowgroup [ref=e87]:86+ - row [ref=e88]:87+ - columnheader [ref=e89]88+ - columnheader "Sim" [ref=e90]89+ - columnheader "Matchup" [ref=e91]90+ - columnheader "Seed" [ref=e92]91+ - columnheader "Expiry" [ref=e93]92+ - columnheader [ref=e94]93+ - rowgroup [ref=e95]:94+ - row [ref=e96]:95+ - cell [ref=e97]:96+ - button "★" [ref=e98] [cursor=pointer]97+ - cell [ref=e99]:98+ - link "sim_VTos-k15PmXk5rrpEbiLP" [ref=e100] [cursor=pointer]:99+ - /url: /s/sim_VTos-k15PmXk5rrpEbiLP100+ - cell "gatherer vs rusher" [ref=e101]101+ - cell "12345" [ref=e102]102+ - cell "pinned" [ref=e103]103+ - cell [ref=e104]:104+ - button "share" [ref=e105] [cursor=pointer]105+ - generic [ref=e106]:106+ - heading "API tokens" [level=2] [ref=e107]107+ - table [ref=e109]:108+ - rowgroup [ref=e110]:109+ - row [ref=e111]:110+ - columnheader "Name" [ref=e112]111+ - columnheader "Created" [ref=e113]112+ - columnheader [ref=e114]113+ - rowgroup [ref=e115]:114+ - row [ref=e116]:115+ - cell "e2e" [ref=e117]116+ - cell "Aug 15" [ref=e118]117+ - cell [ref=e119]:118+ - button "revoke" [ref=e120] [cursor=pointer]119+ - generic [ref=e121]:120+ - textbox "token name (e.g. my-agent)" [ref=e122]121+ - button "Mint token" [ref=e123] [cursor=pointer]122+ - heading "Connect your agent" [level=3] [ref=e124]123+ - generic [ref=e125]: "# Point your agent at Tribes: # docs http://localhost:8787/llms.txt # MCP http://localhost:8787/mcp (streamable HTTP) # REST http://localhost:8787/api/* # Authenticate with: Authorization: Bearer trb_..."124+ - contentinfo [ref=e126]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-31-46-231Z.png +0 -0

Binary file.

A .playwright-cli/page-2026-08-15T22-49-51-089Z.yml +9 -0
A .playwright-cli/page-2026-08-15T22-50-03-361Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e18] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [active] [ref=e22] [cursor=pointer]19+ - button "Restart" [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 1800 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "290"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "300"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "159"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "295"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-50-29-170Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e104] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [ref=e22] [cursor=pointer]19+ - button "Restart" [active] [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 2500 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "290"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "300"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "159"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "295"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A .playwright-cli/page-2026-08-15T22-50-30-326Z.yml +72 -0
@@ -0,0 +1,72 @@
1+- generic [ref=e2]:2+ - generic [ref=e3]:3+ - link "TRIBES" [ref=e4] [cursor=pointer]:4+ - /url: /5+ - generic [ref=e5]: strategy-script fishbowl6+ - generic [ref=e6]: not signed in7+ - main [ref=e7]:8+ - generic [ref=e10]:9+ - heading "Sim sim_VTos-k15PmXk5rrpEbiLP" [level=1] [ref=e11]10+ - generic [ref=e12]: seed 12345 · 2 tribes · map 6411+ - generic [ref=e13]:12+ - generic [ref=e14]:13+ - generic [ref=e17]:14+ - button "Pause" [ref=e104] [cursor=pointer]15+ - button "1×" [ref=e19] [cursor=pointer]16+ - button "4×" [ref=e20] [cursor=pointer]17+ - button "16×" [ref=e21] [cursor=pointer]18+ - button "max" [active] [ref=e22] [cursor=pointer]19+ - button "Restart" [ref=e23] [cursor=pointer]20+ - button "Zoom 1:1" [ref=e24] [cursor=pointer]21+ - combobox [ref=e25]:22+ - 'option "POV: omniscient" [selected]'23+ - 'option "POV: gatherer (slot 0)"'24+ - 'option "POV: rusher (slot 1)"'25+ - generic [ref=e26]: tick 4650 / 1000026+ - paragraph27+ - generic [ref=e30]:28+ - generic [ref=e31]:29+ - generic [ref=e32]:30+ - generic [ref=e34]: gatherer31+ - generic [ref=e35]: Human Hero32+ - generic "over gas budget" [ref=e36]33+ - generic [ref=e37]: alive34+ - generic [ref=e38]:35+ - generic "food" [ref=e39]:36+ - img "food" [ref=e40]37+ - generic [ref=e41]: "290"38+ - generic "wood" [ref=e42]:39+ - img "wood" [ref=e43]40+ - generic [ref=e44]: "300"41+ - generic "stone" [ref=e45]:42+ - img "stone" [ref=e46]43+ - generic [ref=e47]: "300"44+ - generic "population / cap" [ref=e48]:45+ - text: pop46+ - generic [ref=e49]: 10/1047+ - generic "buildings" [ref=e50]:48+ - text: bld49+ - generic [ref=e51]: "160"50+ - generic [ref=e52]:51+ - generic [ref=e53]:52+ - generic [ref=e55]: rusher53+ - generic [ref=e56]: Human Knight54+ - generic "over gas budget" [ref=e57]55+ - generic [ref=e58]: alive56+ - generic [ref=e59]:57+ - generic "food" [ref=e60]:58+ - img "food" [ref=e61]59+ - generic [ref=e62]: "295"60+ - generic "wood" [ref=e63]:61+ - img "wood" [ref=e64]62+ - generic [ref=e65]: "300"63+ - generic "stone" [ref=e66]:64+ - img "stone" [ref=e67]65+ - generic [ref=e68]: "0"66+ - generic "population / cap" [ref=e69]:67+ - text: pop68+ - generic [ref=e70]: 5/1069+ - generic "buildings" [ref=e71]:70+ - text: bld71+ - generic [ref=e72]: "2"72+ - contentinfo [ref=e9]: "Art: Ricardo Machado (beowulf.itch.io) · Icons: SSUGMI · Game assets are licensed for use in this game and are not redistributed. Sims & scripts expire after 14 days unless starred."\ No newline at end of file
A README.md +19 -0
A docs/llms.txt +347 -0
@@ -0,0 +1,347 @@
1+# Tribes — write a bot, throw it in the fishbowl2+3+Tribes is a deterministic strategy-game fishbowl. You submit a JavaScript4+strategy script ("logic"), pit 2-8 scripts against each other in a simulated5+tribal RTS world, and share replayable sims by URL. Everything an agent needs —6+submitting scripts, running sims, starring, these docs — is available over MCP.7+8+Base URL: https://dev.tribes.pub (rolling main). Production releases live at9+versioned hosts (v1-2-3.tribes.pub) plus the apex https://tribes.pub, which10+always runs the latest release; this file is served from `<origin>/llms.txt`11+on every host. Accounts/scripts/sims are shared across all production hosts12+but separate on dev.13+14+## Get an API token15+16+1. Log in on the Tribes site (the base URL) in a browser.17+2. Open the **Tokens** page and create a token. It looks like `trb_...` and is18+ shown once — store it.19+20+The same bearer token works for MCP and for the REST API under `/api/*`.21+22+## Connect over MCP23+24+- Endpoint: `POST <origin>/mcp` — MCP **streamable HTTP**, **stateless25+ 2026-07-28 spec**: every request is a self-contained per-request envelope.26+ There is no `initialize` handshake, no `Mcp-Session-Id`, and no server-side27+ session state. (Clients speaking the older 2025 protocol are still served,28+ statelessly, per request.)29+- Auth: `Authorization: Bearer trb_...` on every request. All MCP tools30+ require auth; there is no anonymous MCP access. Missing/invalid token → HTTP31+ 401 JSON.32+33+Example client config:34+35+```json36+{37+ "mcpServers": {38+ "tribes": {39+ "type": "http",40+ "url": "https://<origin>/mcp",41+ "headers": { "Authorization": "Bearer trb_..." }42+ }43+ }44+}45+```46+47+### Tools48+49+| Tool | Input | Returns (JSON text) |50+| --- | --- | --- |51+| `submit_logic` | `{name, source, lang?='js'}` | `{script: ScriptRecord, report: ValidationReport}` |52+| `list_logic` | — | `ScriptRecord[]` (your scripts, latest versions) |53+| `get_logic` | `{id, version?}` | `ScriptWithSource` (latest version unless `version` given) |54+| `delete_logic` | `{id}` | `{ok: true}` — deletes the script (all versions) |55+| `star` | `{kind: 'script'\|'sim', id}` | `{ok: true}` — pins item, clears its TTL |56+| `unstar` | `{kind, id}` | `{ok: true}` — unpins; TTL applies again |57+| `list_stars` | — | `[{kind, id}]` |58+| `create_sim` | `{scriptIds: string[] (2-8), seed?}` | `SimRecord` incl. share `url` and `expiresAt` |59+| `get_sim` | `{id}` | `SimRecord` (metadata only; no script sources) |60+| `list_sims` | — | `SimRecord[]` |61+| `get_scripting_docs` | — | this document |62+63+Key record fields: `ScriptRecord = {id: 'lg_...', name, lang, version, hash,64+status: 'validated'|'rejected', createdAt, expiresAt|null, starred}`.65+`SimRecord = {id: 'sim_...', seed, config: {tribeCount, mapSize, maxTicks},66+scripts: [{slot, scriptId, scriptName, version, hash}], createdAt,67+expiresAt|null, starred, url}`.68+69+REST mirrors of the same operations live under `/api/*` (same bearer token);70+the sim bundle (metadata + locked script sources) is public at71+`GET /api/sims/<id>/bundle`.72+73+## Scripting reference74+75+### The contract76+77+Your script must define, at top level:78+79+```js80+function tick(state, memory, api) { /* emit orders via api.* */ }81+```82+83+- Called once per simulation tick while your tribe is alive, inside a84+ sandboxed QuickJS VM that persists for the whole sim.85+- `state` — a fresh `TribeView` (below): plain JSON, fog-of-war filtered,86+ read-only snapshot. Mutating it does nothing.87+- `memory` — a plain object that persists across ticks (it lives inside your88+ VM). Must stay JSON-serializable; its serialized size is capped at 64 KiB.89+ Use it for scouting maps, build plans, target lists.90+- `api` — order emitters plus `api.rng()`. Each `api.*` order call appends one91+ Order to this tick's order list. `tick`'s return value is ignored.92+- If `tick` throws, the error is recorded for that tick and ALL orders from93+ that tick are discarded (same as an over-budget tick). Throwing is not94+ fatal — the next tick runs normally — but an erroring tick wastes your turn.95+- Invalid orders (bad ids, unaffordable costs, illegal placement, not your96+ unit, ...) are **silently dropped** by the engine and tallied. Emitting them97+ costs nothing but does nothing.98+99+### `state`: TribeView100+101+| Field | Type | Meaning |102+| --- | --- | --- |103+| `tick` | number | Current tick, 0-based. |104+| `tribeId` | number | Your tribe id. |105+| `mapSize` | number | Map is `mapSize × mapSize` tiles (64). |106+| `resources` | `{food, wood, stone}` | Your stockpile (integers). |107+| `pop` | number | Current population (units). |108+| `popCap` | number | Population cap from town center + houses. |109+| `units` | `UnitView[]` | Your units. |110+| `buildings` | `BuildingView[]` | Your buildings. |111+| `armies` | `ArmyView[]` | Your armies. |112+| `terrain` | string | Explored terrain, row-major `mapSize*mapSize` chars; tile `(x, y)` is `terrain[y * mapSize + x]`. Chars: `?` unexplored, `g` grass, `w` water, `f` forest (wood), `b` berry (food), `r` rock (stone). |113+| `enemies` | `EnemySighting[]` | Enemy entities currently inside your vision. |114+| `aliveTribes` | `number[]` | Alive tribe ids, including yours. |115+116+`UnitView`: `{id, kind: 'villager'|'soldier', x, y, hp, job, armyId}` —117+`armyId` is the army id when grouped, else `null`.118+119+`BuildingView`: `{id, kind, x, y, hp, progress}` — `progress` 0..100;120+100 = complete (incomplete buildings don't function).121+122+`ArmyView`: `{id, stance, unitIds, target}` — `target` is the current waypoint123+`{x, y}` or `null`.124+125+`EnemySighting`: `{tribeId, units, buildings}` — units/buildings carry126+`{id, kind, x, y, hp}` only. Sightings vanish when they leave your vision;127+cache what you need in `memory`.128+129+Enums:130+131+- `Job`: `'idle' | 'gather_food' | 'gather_wood' | 'gather_stone' | 'build'`132+- `BuildingKind`: `'town_center' | 'house' | 'granary' | 'barracks' | 'tower' | 'wall'`133+- `UnitKind`: `'villager' | 'soldier'`134+- `ArmyStance`: `'attack' | 'defend' | 'roam'`135+136+All quantities and coordinates are integers. Coordinates are tile indices in137+`[0, mapSize)`.138+139+### `api`: methods and the Order each emits140+141+| Method | Emits Order | Notes |142+| --- | --- | --- |143+| `api.assign(unitId, job)` | `{kind:'assign', unitId, job}` | Set a villager's job (`Job` above). Villagers path to matching terrain / construction sites on their own. |144+| `api.build(building, x, y)` | `{kind:'build', building, x, y}` | Queue a `BuildingKind` at tile `(x, y)`. Pays the resource cost (costs: see the engine README in `packages/engine`); villagers with job `'build'` construct it. |145+| `api.train(unit)` | `{kind:'train', unit}` | Train a `UnitKind`: villagers at the town center, soldiers at a barracks. Needs resources and pop room. |146+| `api.formArmy(unitIds, stance)` | `{kind:'form_army', unitIds, stance}` | Group soldiers (by unit id) into a new army with an `ArmyStance`. |147+| `api.setStance(armyId, stance)` | `{kind:'set_stance', armyId, stance}` | Change an army's stance. |148+| `api.moveArmy(armyId, x, y)` | `{kind:'move_army', armyId, x, y}` | Set an army's waypoint. |149+| `api.disbandArmy(armyId)` | `{kind:'disband_army', armyId}` | Dissolve the army; members become individual units. |150+| `api.rng()` | — (not an order) | Next value from your tribe's seeded PRNG stream: a float in `[0, 1)`. The **only** permitted randomness. |151+152+### Limits (enforced; from the shared protocol `LIMITS`)153+154+| Limit | Value | Consequence |155+| --- | --- | --- |156+| Map size | 64 × 64 | — |157+| Max ticks per sim | 10,000 | Sim ends at cap; highest score wins. |158+| Tribes per sim | 2–8 | One script per tribe slot. |159+| Script source | 64 KiB UTF-8 | Larger submissions are rejected (stage `size`). |160+| `memory` blob | 64 KiB JSON-serialized | Keep persistent state lean. |161+| Gas per tick | 512 interrupt polls | Over budget → your orders that tick are discarded (idle tick). |162+| Consecutive overruns | 50 | 50 over-budget ticks in a row → your tribe **forfeits**. |163+| VM memory | 32 MiB | Hard cap per tribe VM. |164+| Stars per user | 100 | Across scripts and sims combined. |165+| TTL (unstarred) | 14 days | Scripts and sims expire unless starred. |166+167+Gas is deterministic (QuickJS interrupt-poll counting), not wall-clock time.168+Roughly: tight loops over many entities cost gas; keep per-tick work bounded.169+An occasional heavy tick is safe (it just idles); 50 in a row is death.170+171+### Determinism rules (binding)172+173+- The only randomness is `api.rng()`. `Math.random`, `Date`, timers, and any174+ other wall-clock or environment source are unavailable in the sandbox —175+ relying on them fails validation or crashes your ticks.176+- No I/O, no `fetch`, no imports/`require`, no async. One synchronous `tick`.177+- All sim values are integers; keep order coordinates integral178+ (`Math.floor` your math — `Math` functions are fine, only `Math.random` is179+ banned).180+- Entity iteration order is ascending numeric id, everywhere, always.181+- Same seed + same locked scripts ⇒ bit-identical replay. That's what makes182+ share links trustworthy.183+184+### Validation pipeline (`submit_logic`)185+186+Stages, in order — the report is `{ok, failedStage, detail}`:187+188+1. `size` — source ≤ 64 KiB UTF-8.189+2. `encoding` — valid UTF-8 / no forbidden content encoding.190+3. `syntax` — QuickJS parse of the source.191+4. `smoke` — the script is loaded in a real sandbox VM, must define192+ `function tick(state, memory, api)`, and must survive one tick against a193+ synthetic view without crashing the VM.194+195+`status: 'validated'` scripts can be used in sims; `'rejected'` ones cannot.196+A rejected submission still creates a version — fix and resubmit.197+198+## Game rules summary199+200+- **Resources**: villagers gather `food` (berry tiles `b`), `wood` (forest201+ `f`), `stone` (rock `r`) via `assign` jobs. Exact gather rates, building202+ costs and stats: see the engine README (`packages/engine`).203+- **Population**: training adds units up to `popCap`; houses and the town204+ center raise the cap.205+- **Buildings**: `town_center` (start building, trains villagers), `house`206+ (pop cap), `granary` (resource dropoff), `barracks` (trains soldiers),207+ `tower` (static defense), `wall` (blocks movement). Buildings under208+ construction (`progress < 100`) are inert.209+- **Combat**: soldiers fight in armies; `attack` seeks enemies, `defend`210+ holds near your base, `roam` patrols. Units and buildings have `hp`; kills211+ are tallied per tribe.212+- **Elimination**: lose everything and you're out (`diedAt` records the213+ tick). 50 consecutive gas overruns forfeit the tribe.214+- **Win**: last tribe standing wins (`reason: 'last_standing'`), or at the215+ 10,000-tick cap (`reason: 'tick_cap'`) the highest score wins; exact tie ⇒216+ `winner: null`.217+- **Score**: `pop * 2 + buildings * 5 + kills * 3`.218+219+## Example bot (economy + basic defense)220+221+Compiles against the contract above. Conservative on gas: single pass over222+units, constant work per tick.223+224+```js225+// Gatherer-Guard — economy-first, defends with a small standing army.226+// Contract: top-level `function tick(state, memory, api)`. `memory` persists.227+228+function tick(state, memory, api) {229+ if (!memory.init) {230+ memory.init = true;231+ memory.builtBarracks = false; // remember intent across ticks232+ }233+234+ // --- single pass over units ---------------------------------------------235+ var idleVillagers = [];236+ var looseSoldiers = []; // soldiers not yet in an army237+ for (var i = 0; i < state.units.length; i++) {238+ var u = state.units[i];239+ if (u.kind === 'villager') {240+ if (u.job === 'idle') idleVillagers.push(u);241+ } else if (u.armyId === null) {242+ looseSoldiers.push(u);243+ }244+ }245+246+ // --- economy: put idle villagers to work (60% food / 30% wood / 10% stone)247+ for (var v = 0; v < idleVillagers.length; v++) {248+ var r = api.rng(); // deterministic, seeded per tribe249+ if (r < 0.6) api.assign(idleVillagers[v].id, 'gather_food');250+ else if (r < 0.9) api.assign(idleVillagers[v].id, 'gather_wood');251+ else api.assign(idleVillagers[v].id, 'gather_stone');252+ }253+254+ // --- find our town center (ascending-id order is deterministic) ---------255+ var tc = null;256+ for (var b = 0; b < state.buildings.length; b++) {257+ if (state.buildings[b].kind === 'town_center') { tc = state.buildings[b]; break; }258+ }259+ if (!tc) return; // no town center: nothing left to coordinate from260+261+ // --- growth: train villagers early, keep ahead of the pop cap -----------262+ if (state.pop < state.popCap) {263+ api.train('villager'); // silently dropped if unaffordable — that's fine264+ }265+ if (state.pop >= state.popCap - 2) {266+ // Nudge a house near the TC. Illegal spots are dropped; retry next tick.267+ var hx = tc.x + 2 + Math.floor(api.rng() * 4);268+ var hy = tc.y + 2 + Math.floor(api.rng() * 4);269+ api.build('house', hx, hy);270+ }271+272+ // --- military: one barracks, then a trickle of soldiers -----------------273+ if (!memory.builtBarracks && state.units.length >= 8) {274+ api.build('barracks', tc.x - 3, tc.y + 1);275+ memory.builtBarracks = true; // engine drops it if invalid; cheap to re-flag276+ }277+ api.train('soldier'); // dropped until a barracks is complete & affordable278+279+ // --- defense: pool loose soldiers into a defending army -----------------280+ if (looseSoldiers.length >= 3) {281+ var ids = [];282+ for (var s = 0; s < looseSoldiers.length; s++) ids.push(looseSoldiers[s].id);283+ api.formArmy(ids, 'defend');284+ }285+286+ // --- if raiders are visible, counter-attack the nearest sighting --------287+ if (state.enemies.length > 0) {288+ var foe = state.enemies[0];289+ var spot = foe.units.length > 0 ? foe.units[0]290+ : foe.buildings.length > 0 ? foe.buildings[0] : null;291+ if (spot) {292+ for (var a = 0; a < state.armies.length; a++) {293+ var army = state.armies[a];294+ if (army.unitIds.length >= 3) {295+ api.setStance(army.id, 'attack');296+ api.moveArmy(army.id, spot.x, spot.y);297+ }298+ }299+ }300+ }301+}302+```303+304+## System examples: frozen primitives to fork305+306+Four frozen example scripts ship with the game — three runnable reference307+bots and one helper library. They never expire, are identical for everyone,308+and their source is fully exposed:309+310+- `primitives` (library) — deterministic helpers meant to be pasted above311+ your `tick`: `dist`, `terrainAt`, `findTiles`, `nearest`, `unitsWhere`,312+ `builtCount`, `freeTileNear`, `balanceGatherers`, `rallySoldiers`,313+ `visibleEnemies`. Not runnable alone (defines no `tick`).314+- `gatherer` (bot) — pure economy, never fights.315+- `turtle` (bot) — walls, towers, home-guard defense.316+- `rusher` (bot) — early barracks, scouts, attacks.317+318+Fetch them with the `list_examples` / `get_example` MCP tools or319+`GET /api/examples` (public, no auth). There is deliberately NO injected320+stdlib inside the VM: your submitted source is self-contained, so your script321+hash pins its exact behavior forever. "Forking" an example = take its source,322+edit it, and `submit_logic` it under your own name; from there it versions,323+stars, and expires like any script of yours.324+325+## Lifecycle: TTL, stars, versions, sharing326+327+- **TTL**: unstarred scripts and sims expire **14 days** after creation328+ (`expiresAt`). Expired items are gone — resubmit if you lost one.329+- **Starring**: `star {kind, id}` pins an item (`expiresAt` becomes `null`).330+ Max **100 stars** per user across scripts and sims combined; at the cap,331+ `star` fails with `star_limit` — `unstar` something first.332+- **Versioning**: resubmitting with the **same `name`** keeps the same stable333+ script id and bumps `version` (each version keeps its own `hash`, sha-256 of334+ source). `get_logic {id}` returns the latest version; pass `version` for an335+ older one. A rejected resubmission also records a version.336+- **Sims lock versions**: `create_sim` locks each slot to the script's337+ latest **validated** version at creation time (`{scriptId, version, hash}`).338+ Later resubmits never change an existing sim; replays stay deterministic339+ from `seed` + locked sources. Optional `seed` is a decimal string (1-15340+ digits); omitted → server-generated.341+- **Share URLs**: every sim has `url` = `<origin>/s/<sim_id>` — public, no342+ login needed.343+- **Privacy note**: sim viewers replay locally, which means they **download344+ the locked script sources** of every script in that sim (via the public345+ bundle endpoint). Anyone with a sim link can read the exact source versions346+ locked into it. Don't put secrets in scripts, and don't share sims whose347+ strategies you want to keep private.
A docs/scripting.md +59 -0
@@ -0,0 +1,59 @@
1+# Writing a Tribes bot2+3+Tribes bots are single JavaScript files. You write one function; the game4+calls it once per tick for your tribe, in a sandboxed VM, and applies the5+orders you emit. The complete machine-oriented reference (every field, every6+limit, a full example bot) lives in [`llms.txt`](./llms.txt) — this page is7+the short human tour.8+9+## The one function you write10+11+```js12+function tick(state, memory, api) {13+ // read state, remember things in memory, emit orders via api.*14+}15+```16+17+- **`state`** — a fresh, read-only snapshot of everything your tribe can see:18+ your `resources` (food/wood/stone), `pop`/`popCap`, your `units`,19+ `buildings` and `armies`, explored `terrain` (a `64*64` character string:20+ `?` unexplored, `g` grass, `w` water, `f` forest, `b` berry, `r` rock), and21+ `enemies` currently in vision. Fog of war is real — cache sightings in22+ `memory` if you want to remember them.23+- **`memory`** — a plain JSON object that survives between ticks (up to 64 KiB24+ serialized). Your only persistent state.25+- **`api`** — how you act. Each call emits one order:26+ `api.assign(unitId, job)`, `api.build(kind, x, y)`, `api.train(kind)`,27+ `api.formArmy(unitIds, stance)`, `api.setStance(armyId, stance)`,28+ `api.moveArmy(armyId, x, y)`, `api.disbandArmy(armyId)` — plus29+ `api.rng()`, your only source of randomness. Invalid or unaffordable orders30+ are silently dropped, so speculative orders are a cheap idiom.31+32+## The rules that bite33+34+- **Determinism**: no `Math.random`, no `Date`, no I/O, no async. Same seed +35+ same scripts ⇒ identical replay. Use `api.rng()`.36+- **Gas**: 512 deterministic gas polls per tick. Go over → that tick's orders37+ are discarded. Go over **50 ticks in a row** → your tribe forfeits.38+- **Size**: source ≤ 64 KiB; VM memory ≤ 32 MiB.39+- **Validation**: submissions pass size → encoding → syntax → smoke-test40+ stages before they can enter a sim.41+42+## Playing43+44+1. Log in on the site and mint an API token (Tokens page), or use the web UI.45+2. Submit your script (`submit_logic` over MCP, or `POST /api/logic`).46+ Resubmitting the same name bumps the version.47+3. Start a sim with 2–8 scripts. The sim locks each script's current48+ validated version, so you can keep iterating without breaking old replays.49+4. Share the `/s/<id>` link. Anyone can watch — and **anyone with the link50+ can read the locked script sources**, so share accordingly.51+5. Unstarred scripts and sims expire after 14 days; star (up to 100 items) to52+ keep them forever.53+54+**Winning**: be the last tribe standing, or have the best score at the55+10,000-tick cap. Score = `pop × 2 + buildings × 5 + kills × 3`.56+57+For exact building costs and combat numbers, see the engine README58+(`packages/engine`). For the full field-by-field contract and a commented59+starter bot, read [`llms.txt`](./llms.txt).
A migrations/0001_init.sql +77 -0
@@ -0,0 +1,77 @@
1+-- Tribes initial schema. Times are unix epoch seconds (INTEGER).2+CREATE TABLE users (3+ id TEXT PRIMARY KEY,4+ external_id TEXT NOT NULL UNIQUE, -- shoo pairwise_sub or 'local:<name>'5+ name TEXT,6+ created_at INTEGER NOT NULL7+);8+9+CREATE TABLE api_tokens (10+ id TEXT PRIMARY KEY,11+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,12+ token_hash TEXT NOT NULL UNIQUE, -- sha-256 hex of the full token13+ name TEXT NOT NULL,14+ created_at INTEGER NOT NULL15+);16+17+-- Content-addressed script sources. Swept when unreferenced by live rows.18+CREATE TABLE blobs (19+ hash TEXT PRIMARY KEY, -- sha-256 hex20+ source TEXT NOT NULL,21+ created_at INTEGER NOT NULL22+);23+24+CREATE TABLE scripts (25+ id TEXT PRIMARY KEY, -- lg_<nanoid>26+ owner_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,27+ name TEXT NOT NULL,28+ lang TEXT NOT NULL DEFAULT 'js',29+ version INTEGER NOT NULL, -- latest version number30+ hash TEXT NOT NULL REFERENCES blobs(hash),31+ status TEXT NOT NULL CHECK (status IN ('validated', 'rejected')),32+ error_report TEXT,33+ created_at INTEGER NOT NULL,34+ expires_at INTEGER, -- NULL only transitionally; liveness = starred OR expires_at > now35+ UNIQUE (owner_id, name)36+);37+38+CREATE TABLE script_versions (39+ script_id TEXT NOT NULL REFERENCES scripts(id) ON DELETE CASCADE,40+ version INTEGER NOT NULL,41+ hash TEXT NOT NULL REFERENCES blobs(hash),42+ created_at INTEGER NOT NULL,43+ PRIMARY KEY (script_id, version)44+);45+46+CREATE TABLE sims (47+ id TEXT PRIMARY KEY, -- sim_<nanoid>48+ creator_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,49+ seed TEXT NOT NULL,50+ config_json TEXT NOT NULL,51+ created_at INTEGER NOT NULL,52+ expires_at INTEGER53+);54+55+CREATE TABLE sim_scripts (56+ sim_id TEXT NOT NULL REFERENCES sims(id) ON DELETE CASCADE,57+ slot INTEGER NOT NULL,58+ script_id TEXT NOT NULL, -- intentionally NOT a FK: sim outlives script via blob pin59+ script_name TEXT NOT NULL,60+ version INTEGER NOT NULL,61+ hash TEXT NOT NULL REFERENCES blobs(hash),62+ PRIMARY KEY (sim_id, slot)63+);64+65+CREATE TABLE stars (66+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,67+ kind TEXT NOT NULL CHECK (kind IN ('script', 'sim')),68+ target_id TEXT NOT NULL,69+ created_at INTEGER NOT NULL,70+ PRIMARY KEY (user_id, kind, target_id)71+);72+73+CREATE INDEX idx_scripts_owner ON scripts(owner_id);74+CREATE INDEX idx_sims_creator ON sims(creator_id);75+CREATE INDEX idx_stars_target ON stars(kind, target_id);76+CREATE INDEX idx_sim_scripts_hash ON sim_scripts(hash);77+CREATE INDEX idx_script_versions_hash ON script_versions(hash);
A package-lock.json +6607 -0
@@ -0,0 +1,6607 @@
1+{2+ "name": "tribes",3+ "lockfileVersion": 3,4+ "requires": true,5+ "packages": {6+ "": {7+ "name": "tribes",8+ "workspaces": [9+ "packages/*",10+ "worker",11+ "web"12+ ],13+ "devDependencies": {14+ "typescript": "^5.6.0",15+ "vitest": "^3.0.0",16+ "wrangler": "^4.123.0"17+ }18+ },19+ "node_modules/@babel/code-frame": {20+ "version": "8.0.0",21+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz",22+ "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==",23+ "license": "MIT",24+ "dependencies": {25+ "@babel/helper-validator-identifier": "^8.0.0",26+ "js-tokens": "^10.0.0"27+ },28+ "engines": {29+ "node": "^22.18.0 || >=24.11.0"30+ }31+ },32+ "node_modules/@babel/code-frame/node_modules/js-tokens": {33+ "version": "10.0.0",34+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",35+ "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",36+ "license": "MIT"37+ },38+ "node_modules/@babel/compat-data": {39+ "version": "8.0.0",40+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0.tgz",41+ "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==",42+ "license": "MIT",43+ "peer": true,44+ "engines": {45+ "node": "^22.18.0 || >=24.11.0"46+ }47+ },48+ "node_modules/@babel/core": {49+ "version": "8.0.1",50+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.1.tgz",51+ "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==",52+ "license": "MIT",53+ "peer": true,54+ "dependencies": {55+ "@babel/code-frame": "^8.0.0",56+ "@babel/generator": "^8.0.0",57+ "@babel/helper-compilation-targets": "^8.0.0",58+ "@babel/helpers": "^8.0.0",59+ "@babel/parser": "^8.0.0",60+ "@babel/template": "^8.0.0",61+ "@babel/traverse": "^8.0.0",62+ "@babel/types": "^8.0.0",63+ "@types/gensync": "^1.0.5",64+ "convert-source-map": "^2.0.0",65+ "empathic": "^2.0.1",66+ "gensync": "^1.0.0-beta.2",67+ "import-meta-resolve": "^4.2.0",68+ "json5": "^2.2.3",69+ "obug": "^2.1.1",70+ "semver": "^7.7.3"71+ },72+ "engines": {73+ "node": "^22.18.0 || >=24.11.0"74+ },75+ "funding": {76+ "type": "opencollective",77+ "url": "https://opencollective.com/babel"78+ }79+ },80+ "node_modules/@babel/generator": {81+ "version": "8.0.0",82+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz",83+ "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==",84+ "license": "MIT",85+ "dependencies": {86+ "@babel/parser": "^8.0.0",87+ "@babel/types": "^8.0.0",88+ "@jridgewell/gen-mapping": "^0.3.12",89+ "@jridgewell/trace-mapping": "^0.3.28",90+ "@types/jsesc": "^2.5.0",91+ "jsesc": "^3.0.2"92+ },93+ "engines": {94+ "node": "^22.18.0 || >=24.11.0"95+ }96+ },97+ "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": {98+ "version": "0.3.31",99+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",100+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",101+ "license": "MIT",102+ "dependencies": {103+ "@jridgewell/resolve-uri": "^3.1.0",104+ "@jridgewell/sourcemap-codec": "^1.4.14"105+ }106+ },107+ "node_modules/@babel/helper-annotate-as-pure": {108+ "version": "8.0.0",109+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz",110+ "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==",111+ "license": "MIT",112+ "dependencies": {113+ "@babel/types": "^8.0.0"114+ },115+ "engines": {116+ "node": "^22.18.0 || >=24.11.0"117+ }118+ },119+ "node_modules/@babel/helper-compilation-targets": {120+ "version": "8.0.0",121+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz",122+ "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==",123+ "license": "MIT",124+ "peer": true,125+ "dependencies": {126+ "@babel/compat-data": "^8.0.0",127+ "@babel/helper-validator-option": "^8.0.0",128+ "browserslist": "^4.24.0",129+ "lru-cache": "^11.0.0",130+ "semver": "^7.7.3"131+ },132+ "engines": {133+ "node": "^22.18.0 || >=24.11.0"134+ }135+ },136+ "node_modules/@babel/helper-create-class-features-plugin": {137+ "version": "8.0.1",138+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.1.tgz",139+ "integrity": "sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==",140+ "license": "MIT",141+ "dependencies": {142+ "@babel/helper-annotate-as-pure": "^8.0.0",143+ "@babel/helper-member-expression-to-functions": "^8.0.0",144+ "@babel/helper-optimise-call-expression": "^8.0.0",145+ "@babel/helper-replace-supers": "^8.0.1",146+ "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0",147+ "@babel/traverse": "^8.0.0",148+ "semver": "^7.7.3"149+ },150+ "engines": {151+ "node": "^22.18.0 || >=24.11.0"152+ },153+ "peerDependencies": {154+ "@babel/core": "^8.0.0"155+ }156+ },157+ "node_modules/@babel/helper-globals": {158+ "version": "8.0.0",159+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz",160+ "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==",161+ "license": "MIT",162+ "engines": {163+ "node": "^22.18.0 || >=24.11.0"164+ }165+ },166+ "node_modules/@babel/helper-member-expression-to-functions": {167+ "version": "8.0.0",168+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.0.tgz",169+ "integrity": "sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==",170+ "license": "MIT",171+ "dependencies": {172+ "@babel/traverse": "^8.0.0",173+ "@babel/types": "^8.0.0"174+ },175+ "engines": {176+ "node": "^22.18.0 || >=24.11.0"177+ }178+ },179+ "node_modules/@babel/helper-optimise-call-expression": {180+ "version": "8.0.0",181+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz",182+ "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==",183+ "license": "MIT",184+ "dependencies": {185+ "@babel/types": "^8.0.0"186+ },187+ "engines": {188+ "node": "^22.18.0 || >=24.11.0"189+ }190+ },191+ "node_modules/@babel/helper-plugin-utils": {192+ "version": "8.0.1",193+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz",194+ "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==",195+ "license": "MIT",196+ "engines": {197+ "node": "^22.18.0 || >=24.11.0"198+ },199+ "peerDependencies": {200+ "@babel/core": "^8.0.0"201+ }202+ },203+ "node_modules/@babel/helper-replace-supers": {204+ "version": "8.0.1",205+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz",206+ "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==",207+ "license": "MIT",208+ "dependencies": {209+ "@babel/helper-member-expression-to-functions": "^8.0.0",210+ "@babel/helper-optimise-call-expression": "^8.0.0",211+ "@babel/traverse": "^8.0.0"212+ },213+ "engines": {214+ "node": "^22.18.0 || >=24.11.0"215+ },216+ "peerDependencies": {217+ "@babel/core": "^8.0.0"218+ }219+ },220+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {221+ "version": "8.0.0",222+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz",223+ "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==",224+ "license": "MIT",225+ "dependencies": {226+ "@babel/traverse": "^8.0.0",227+ "@babel/types": "^8.0.0"228+ },229+ "engines": {230+ "node": "^22.18.0 || >=24.11.0"231+ }232+ },233+ "node_modules/@babel/helper-string-parser": {234+ "version": "8.0.0",235+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",236+ "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",237+ "license": "MIT",238+ "engines": {239+ "node": "^22.18.0 || >=24.11.0"240+ }241+ },242+ "node_modules/@babel/helper-validator-identifier": {243+ "version": "8.0.4",244+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",245+ "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",246+ "license": "MIT",247+ "engines": {248+ "node": "^22.18.0 || >=24.11.0"249+ }250+ },251+ "node_modules/@babel/helper-validator-option": {252+ "version": "8.0.0",253+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz",254+ "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==",255+ "license": "MIT",256+ "peer": true,257+ "engines": {258+ "node": "^22.18.0 || >=24.11.0"259+ }260+ },261+ "node_modules/@babel/helpers": {262+ "version": "8.0.0",263+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0.tgz",264+ "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==",265+ "license": "MIT",266+ "peer": true,267+ "dependencies": {268+ "@babel/template": "^8.0.0",269+ "@babel/types": "^8.0.0"270+ },271+ "engines": {272+ "node": "^22.18.0 || >=24.11.0"273+ }274+ },275+ "node_modules/@babel/parser": {276+ "version": "8.0.4",277+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz",278+ "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==",279+ "license": "MIT",280+ "dependencies": {281+ "@babel/types": "^8.0.4"282+ },283+ "bin": {284+ "parser": "bin/babel-parser.js"285+ },286+ "engines": {287+ "node": "^22.18.0 || >=24.11.0"288+ }289+ },290+ "node_modules/@babel/plugin-proposal-decorators": {291+ "version": "8.0.2",292+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz",293+ "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==",294+ "license": "MIT",295+ "dependencies": {296+ "@babel/helper-create-class-features-plugin": "^8.0.1",297+ "@babel/helper-plugin-utils": "^8.0.1",298+ "@babel/plugin-syntax-decorators": "^8.0.1"299+ },300+ "engines": {301+ "node": "^22.18.0 || >=24.11.0"302+ },303+ "peerDependencies": {304+ "@babel/core": "^8.0.0"305+ }306+ },307+ "node_modules/@babel/plugin-syntax-decorators": {308+ "version": "8.0.1",309+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz",310+ "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==",311+ "license": "MIT",312+ "dependencies": {313+ "@babel/helper-plugin-utils": "^8.0.1"314+ },315+ "engines": {316+ "node": "^22.18.0 || >=24.11.0"317+ },318+ "peerDependencies": {319+ "@babel/core": "^8.0.0"320+ }321+ },322+ "node_modules/@babel/runtime": {323+ "version": "7.29.7",324+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",325+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",326+ "license": "MIT",327+ "engines": {328+ "node": ">=6.9.0"329+ }330+ },331+ "node_modules/@babel/runtime-corejs3": {332+ "version": "7.29.7",333+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.7.tgz",334+ "integrity": "sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==",335+ "license": "MIT",336+ "dependencies": {337+ "core-js-pure": "^3.48.0"338+ },339+ "engines": {340+ "node": ">=6.9.0"341+ }342+ },343+ "node_modules/@babel/template": {344+ "version": "8.0.0",345+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz",346+ "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==",347+ "license": "MIT",348+ "dependencies": {349+ "@babel/code-frame": "^8.0.0",350+ "@babel/parser": "^8.0.0",351+ "@babel/types": "^8.0.0"352+ },353+ "engines": {354+ "node": "^22.18.0 || >=24.11.0"355+ }356+ },357+ "node_modules/@babel/traverse": {358+ "version": "8.0.4",359+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.4.tgz",360+ "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==",361+ "license": "MIT",362+ "dependencies": {363+ "@babel/code-frame": "^8.0.0",364+ "@babel/generator": "^8.0.0",365+ "@babel/helper-globals": "^8.0.0",366+ "@babel/parser": "^8.0.4",367+ "@babel/template": "^8.0.0",368+ "@babel/types": "^8.0.4",369+ "obug": "^2.1.1"370+ },371+ "engines": {372+ "node": "^22.18.0 || >=24.11.0"373+ }374+ },375+ "node_modules/@babel/types": {376+ "version": "8.0.4",377+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz",378+ "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==",379+ "license": "MIT",380+ "dependencies": {381+ "@babel/helper-string-parser": "^8.0.0",382+ "@babel/helper-validator-identifier": "^8.0.4"383+ },384+ "engines": {385+ "node": "^22.18.0 || >=24.11.0"386+ }387+ },388+ "node_modules/@cf-wasm/quickjs": {389+ "version": "0.3.0",390+ "resolved": "https://registry.npmjs.org/@cf-wasm/quickjs/-/quickjs-0.3.0.tgz",391+ "integrity": "sha512-uYnpucJ+xe6Nka5oVYxj4v6YjTovbBScrAKEy0zAB5ZJ2K8MGqGsqC064Fr1mu+M3a8GT438pKqHltHm8QzAqQ==",392+ "license": "MIT",393+ "dependencies": {394+ "@jitl/quickjs-wasmfile-debug-sync": "0.32.0",395+ "@jitl/quickjs-wasmfile-release-sync": "0.32.0",396+ "quickjs-emscripten-core": "0.32.0"397+ }398+ },399+ "node_modules/@cfworker/json-schema": {400+ "version": "4.1.1",401+ "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz",402+ "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",403+ "license": "MIT"404+ },405+ "node_modules/@cloudflare/kv-asset-handler": {406+ "version": "0.5.0",407+ "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz",408+ "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==",409+ "dev": true,410+ "license": "MIT OR Apache-2.0",411+ "engines": {412+ "node": ">=22.0.0"413+ }414+ },415+ "node_modules/@cloudflare/unenv-preset": {416+ "version": "2.16.1",417+ "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz",418+ "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==",419+ "dev": true,420+ "license": "MIT OR Apache-2.0",421+ "peerDependencies": {422+ "unenv": "2.0.0-rc.24",423+ "workerd": ">1.20260305.0 <2.0.0-0"424+ },425+ "peerDependenciesMeta": {426+ "workerd": {427+ "optional": true428+ }429+ }430+ },431+ "node_modules/@cloudflare/workerd-darwin-64": {432+ "version": "1.20260811.1",433+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260811.1.tgz",434+ "integrity": "sha512-i5jqz+ywtOefr0AJbiAc8qxBLfSim/B0WJG7aW3B+pWnoVfMJdUQvi+BWcFKZJ0MoCci3KadTx6g31VfuEEqpQ==",435+ "cpu": [436+ "x64"437+ ],438+ "dev": true,439+ "license": "Apache-2.0",440+ "optional": true,441+ "os": [442+ "darwin"443+ ],444+ "engines": {445+ "node": ">=16"446+ }447+ },448+ "node_modules/@cloudflare/workerd-darwin-arm64": {449+ "version": "1.20260811.1",450+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260811.1.tgz",451+ "integrity": "sha512-NoOUM/nvaDdm2Onlnz33FikWjtatzulNtvwvy4xs0IrHaTCHwC0c8NwIt6s+AI13FkDs02/vm2I3GTPLCT9+hQ==",452+ "cpu": [453+ "arm64"454+ ],455+ "dev": true,456+ "license": "Apache-2.0",457+ "optional": true,458+ "os": [459+ "darwin"460+ ],461+ "engines": {462+ "node": ">=16"463+ }464+ },465+ "node_modules/@cloudflare/workerd-linux-64": {466+ "version": "1.20260811.1",467+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260811.1.tgz",468+ "integrity": "sha512-sdYq2jL1AD1supa3fsi5O4zTB28wSjvTHj7Migh6/ts8EROPdvrSwv+rdGHhv8HJNAz/wbIAY3wZsi1Rw4uUIg==",469+ "cpu": [470+ "x64"471+ ],472+ "dev": true,473+ "license": "Apache-2.0",474+ "optional": true,475+ "os": [476+ "linux"477+ ],478+ "engines": {479+ "node": ">=16"480+ }481+ },482+ "node_modules/@cloudflare/workerd-linux-arm64": {483+ "version": "1.20260811.1",484+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260811.1.tgz",485+ "integrity": "sha512-RIRv4shbu1kg05sD+DHTpSFCNnb5Dl2SkPDMUykqZa508tkPqe7VVw7gO0Q5msTBGyL0FfFrLuRxwwfA8u5Sow==",486+ "cpu": [487+ "arm64"488+ ],489+ "dev": true,490+ "license": "Apache-2.0",491+ "optional": true,492+ "os": [493+ "linux"494+ ],495+ "engines": {496+ "node": ">=16"497+ }498+ },499+ "node_modules/@cloudflare/workerd-windows-64": {500+ "version": "1.20260811.1",501+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260811.1.tgz",502+ "integrity": "sha512-g6VquwjASlYAibcNW/0E6Zszht4qLkmnXOGwIjjRHl2A0Qz48kVeMcGvyH6eA0G9U3OzZojjYFpP+YeyQmmdjw==",503+ "cpu": [504+ "x64"505+ ],506+ "dev": true,507+ "license": "Apache-2.0",508+ "optional": true,509+ "os": [510+ "win32"511+ ],512+ "engines": {513+ "node": ">=16"514+ }515+ },516+ "node_modules/@cloudflare/workers-types": {517+ "version": "5.20260815.1",518+ "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-5.20260815.1.tgz",519+ "integrity": "sha512-btpW8rEgeJKcxDlBWKxzjLLUHxzUWqXZY8O+DmM6NcSHadUQN5I71z6Q/yAKCTJyGS7adBpXzlsZSFbosLnECQ==",520+ "license": "MIT OR Apache-2.0",521+ "peer": true522+ },523+ "node_modules/@cspotcode/source-map-support": {524+ "version": "0.8.1",525+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",526+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",527+ "dev": true,528+ "license": "MIT",529+ "dependencies": {530+ "@jridgewell/trace-mapping": "0.3.9"531+ },532+ "engines": {533+ "node": ">=12"534+ }535+ },536+ "node_modules/@emnapi/runtime": {537+ "version": "1.11.3",538+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",539+ "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",540+ "dev": true,541+ "license": "MIT",542+ "optional": true,543+ "dependencies": {544+ "tslib": "^2.4.0"545+ }546+ },547+ "node_modules/@esbuild/aix-ppc64": {548+ "version": "0.28.2",549+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",550+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",551+ "cpu": [552+ "ppc64"553+ ],554+ "license": "MIT",555+ "optional": true,556+ "os": [557+ "aix"558+ ],559+ "engines": {560+ "node": ">=18"561+ }562+ },563+ "node_modules/@esbuild/android-arm": {564+ "version": "0.28.2",565+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",566+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",567+ "cpu": [568+ "arm"569+ ],570+ "license": "MIT",571+ "optional": true,572+ "os": [573+ "android"574+ ],575+ "engines": {576+ "node": ">=18"577+ }578+ },579+ "node_modules/@esbuild/android-arm64": {580+ "version": "0.28.2",581+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",582+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",583+ "cpu": [584+ "arm64"585+ ],586+ "license": "MIT",587+ "optional": true,588+ "os": [589+ "android"590+ ],591+ "engines": {592+ "node": ">=18"593+ }594+ },595+ "node_modules/@esbuild/android-x64": {596+ "version": "0.28.2",597+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",598+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",599+ "cpu": [600+ "x64"601+ ],602+ "license": "MIT",603+ "optional": true,604+ "os": [605+ "android"606+ ],607+ "engines": {608+ "node": ">=18"609+ }610+ },611+ "node_modules/@esbuild/darwin-arm64": {612+ "version": "0.28.2",613+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",614+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",615+ "cpu": [616+ "arm64"617+ ],618+ "license": "MIT",619+ "optional": true,620+ "os": [621+ "darwin"622+ ],623+ "engines": {624+ "node": ">=18"625+ }626+ },627+ "node_modules/@esbuild/darwin-x64": {628+ "version": "0.28.2",629+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",630+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",631+ "cpu": [632+ "x64"633+ ],634+ "license": "MIT",635+ "optional": true,636+ "os": [637+ "darwin"638+ ],639+ "engines": {640+ "node": ">=18"641+ }642+ },643+ "node_modules/@esbuild/freebsd-arm64": {644+ "version": "0.28.2",645+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",646+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",647+ "cpu": [648+ "arm64"649+ ],650+ "license": "MIT",651+ "optional": true,652+ "os": [653+ "freebsd"654+ ],655+ "engines": {656+ "node": ">=18"657+ }658+ },659+ "node_modules/@esbuild/freebsd-x64": {660+ "version": "0.28.2",661+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",662+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",663+ "cpu": [664+ "x64"665+ ],666+ "license": "MIT",667+ "optional": true,668+ "os": [669+ "freebsd"670+ ],671+ "engines": {672+ "node": ">=18"673+ }674+ },675+ "node_modules/@esbuild/linux-arm": {676+ "version": "0.28.2",677+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",678+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",679+ "cpu": [680+ "arm"681+ ],682+ "license": "MIT",683+ "optional": true,684+ "os": [685+ "linux"686+ ],687+ "engines": {688+ "node": ">=18"689+ }690+ },691+ "node_modules/@esbuild/linux-arm64": {692+ "version": "0.28.2",693+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",694+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",695+ "cpu": [696+ "arm64"697+ ],698+ "license": "MIT",699+ "optional": true,700+ "os": [701+ "linux"702+ ],703+ "engines": {704+ "node": ">=18"705+ }706+ },707+ "node_modules/@esbuild/linux-ia32": {708+ "version": "0.28.2",709+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",710+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",711+ "cpu": [712+ "ia32"713+ ],714+ "license": "MIT",715+ "optional": true,716+ "os": [717+ "linux"718+ ],719+ "engines": {720+ "node": ">=18"721+ }722+ },723+ "node_modules/@esbuild/linux-loong64": {724+ "version": "0.28.2",725+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",726+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",727+ "cpu": [728+ "loong64"729+ ],730+ "license": "MIT",731+ "optional": true,732+ "os": [733+ "linux"734+ ],735+ "engines": {736+ "node": ">=18"737+ }738+ },739+ "node_modules/@esbuild/linux-mips64el": {740+ "version": "0.28.2",741+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",742+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",743+ "cpu": [744+ "mips64el"745+ ],746+ "license": "MIT",747+ "optional": true,748+ "os": [749+ "linux"750+ ],751+ "engines": {752+ "node": ">=18"753+ }754+ },755+ "node_modules/@esbuild/linux-ppc64": {756+ "version": "0.28.2",757+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",758+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",759+ "cpu": [760+ "ppc64"761+ ],762+ "license": "MIT",763+ "optional": true,764+ "os": [765+ "linux"766+ ],767+ "engines": {768+ "node": ">=18"769+ }770+ },771+ "node_modules/@esbuild/linux-riscv64": {772+ "version": "0.28.2",773+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",774+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",775+ "cpu": [776+ "riscv64"777+ ],778+ "license": "MIT",779+ "optional": true,780+ "os": [781+ "linux"782+ ],783+ "engines": {784+ "node": ">=18"785+ }786+ },787+ "node_modules/@esbuild/linux-s390x": {788+ "version": "0.28.2",789+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",790+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",791+ "cpu": [792+ "s390x"793+ ],794+ "license": "MIT",795+ "optional": true,796+ "os": [797+ "linux"798+ ],799+ "engines": {800+ "node": ">=18"801+ }802+ },803+ "node_modules/@esbuild/linux-x64": {804+ "version": "0.28.2",805+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",806+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",807+ "cpu": [808+ "x64"809+ ],810+ "license": "MIT",811+ "optional": true,812+ "os": [813+ "linux"814+ ],815+ "engines": {816+ "node": ">=18"817+ }818+ },819+ "node_modules/@esbuild/netbsd-arm64": {820+ "version": "0.28.2",821+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",822+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",823+ "cpu": [824+ "arm64"825+ ],826+ "license": "MIT",827+ "optional": true,828+ "os": [829+ "netbsd"830+ ],831+ "engines": {832+ "node": ">=18"833+ }834+ },835+ "node_modules/@esbuild/netbsd-x64": {836+ "version": "0.28.2",837+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",838+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",839+ "cpu": [840+ "x64"841+ ],842+ "license": "MIT",843+ "optional": true,844+ "os": [845+ "netbsd"846+ ],847+ "engines": {848+ "node": ">=18"849+ }850+ },851+ "node_modules/@esbuild/openbsd-arm64": {852+ "version": "0.28.2",853+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",854+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",855+ "cpu": [856+ "arm64"857+ ],858+ "license": "MIT",859+ "optional": true,860+ "os": [861+ "openbsd"862+ ],863+ "engines": {864+ "node": ">=18"865+ }866+ },867+ "node_modules/@esbuild/openbsd-x64": {868+ "version": "0.28.2",869+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",870+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",871+ "cpu": [872+ "x64"873+ ],874+ "license": "MIT",875+ "optional": true,876+ "os": [877+ "openbsd"878+ ],879+ "engines": {880+ "node": ">=18"881+ }882+ },883+ "node_modules/@esbuild/openharmony-arm64": {884+ "version": "0.28.2",885+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",886+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",887+ "cpu": [888+ "arm64"889+ ],890+ "license": "MIT",891+ "optional": true,892+ "os": [893+ "openharmony"894+ ],895+ "engines": {896+ "node": ">=18"897+ }898+ },899+ "node_modules/@esbuild/sunos-x64": {900+ "version": "0.28.2",901+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",902+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",903+ "cpu": [904+ "x64"905+ ],906+ "license": "MIT",907+ "optional": true,908+ "os": [909+ "sunos"910+ ],911+ "engines": {912+ "node": ">=18"913+ }914+ },915+ "node_modules/@esbuild/win32-arm64": {916+ "version": "0.28.2",917+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",918+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",919+ "cpu": [920+ "arm64"921+ ],922+ "license": "MIT",923+ "optional": true,924+ "os": [925+ "win32"926+ ],927+ "engines": {928+ "node": ">=18"929+ }930+ },931+ "node_modules/@esbuild/win32-ia32": {932+ "version": "0.28.2",933+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",934+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",935+ "cpu": [936+ "ia32"937+ ],938+ "license": "MIT",939+ "optional": true,940+ "os": [941+ "win32"942+ ],943+ "engines": {944+ "node": ">=18"945+ }946+ },947+ "node_modules/@esbuild/win32-x64": {948+ "version": "0.28.2",949+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",950+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",951+ "cpu": [952+ "x64"953+ ],954+ "license": "MIT",955+ "optional": true,956+ "os": [957+ "win32"958+ ],959+ "engines": {960+ "node": ">=18"961+ }962+ },963+ "node_modules/@hono/node-server": {964+ "version": "2.1.1",965+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz",966+ "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==",967+ "license": "MIT",968+ "peer": true,969+ "engines": {970+ "node": ">=20"971+ },972+ "peerDependencies": {973+ "hono": "^4"974+ }975+ },976+ "node_modules/@img/colour": {977+ "version": "1.1.0",978+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",979+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",980+ "dev": true,981+ "license": "MIT",982+ "engines": {983+ "node": ">=18"984+ }985+ },986+ "node_modules/@img/sharp-darwin-arm64": {987+ "version": "0.35.2",988+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz",989+ "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==",990+ "cpu": [991+ "arm64"992+ ],993+ "dev": true,994+ "license": "Apache-2.0",995+ "optional": true,996+ "os": [997+ "darwin"998+ ],999+ "engines": {1000+ "node": ">=20.9.0"1001+ },1002+ "funding": {1003+ "url": "https://opencollective.com/libvips"1004+ },1005+ "optionalDependencies": {1006+ "@img/sharp-libvips-darwin-arm64": "1.3.1"1007+ }1008+ },1009+ "node_modules/@img/sharp-darwin-x64": {1010+ "version": "0.35.2",1011+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz",1012+ "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==",1013+ "cpu": [1014+ "x64"1015+ ],1016+ "dev": true,1017+ "license": "Apache-2.0",1018+ "optional": true,1019+ "os": [1020+ "darwin"1021+ ],1022+ "engines": {1023+ "node": ">=20.9.0"1024+ },1025+ "funding": {1026+ "url": "https://opencollective.com/libvips"1027+ },1028+ "optionalDependencies": {1029+ "@img/sharp-libvips-darwin-x64": "1.3.1"1030+ }1031+ },1032+ "node_modules/@img/sharp-freebsd-wasm32": {1033+ "version": "0.35.2",1034+ "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz",1035+ "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==",1036+ "dev": true,1037+ "license": "Apache-2.0",1038+ "optional": true,1039+ "os": [1040+ "freebsd"1041+ ],1042+ "dependencies": {1043+ "@img/sharp-wasm32": "0.35.2"1044+ },1045+ "engines": {1046+ "node": ">=20.9.0"1047+ },1048+ "funding": {1049+ "url": "https://opencollective.com/libvips"1050+ }1051+ },1052+ "node_modules/@img/sharp-libvips-darwin-arm64": {1053+ "version": "1.3.1",1054+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz",1055+ "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==",1056+ "cpu": [1057+ "arm64"1058+ ],1059+ "dev": true,1060+ "license": "LGPL-3.0-or-later",1061+ "optional": true,1062+ "os": [1063+ "darwin"1064+ ],1065+ "funding": {1066+ "url": "https://opencollective.com/libvips"1067+ }1068+ },1069+ "node_modules/@img/sharp-libvips-darwin-x64": {1070+ "version": "1.3.1",1071+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz",1072+ "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==",1073+ "cpu": [1074+ "x64"1075+ ],1076+ "dev": true,1077+ "license": "LGPL-3.0-or-later",1078+ "optional": true,1079+ "os": [1080+ "darwin"1081+ ],1082+ "funding": {1083+ "url": "https://opencollective.com/libvips"1084+ }1085+ },1086+ "node_modules/@img/sharp-libvips-linux-arm": {1087+ "version": "1.3.1",1088+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz",1089+ "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==",1090+ "cpu": [1091+ "arm"1092+ ],1093+ "dev": true,1094+ "libc": [1095+ "glibc"1096+ ],1097+ "license": "LGPL-3.0-or-later",1098+ "optional": true,1099+ "os": [1100+ "linux"1101+ ],1102+ "funding": {1103+ "url": "https://opencollective.com/libvips"1104+ }1105+ },1106+ "node_modules/@img/sharp-libvips-linux-arm64": {1107+ "version": "1.3.1",1108+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz",1109+ "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==",1110+ "cpu": [1111+ "arm64"1112+ ],1113+ "dev": true,1114+ "libc": [1115+ "glibc"1116+ ],1117+ "license": "LGPL-3.0-or-later",1118+ "optional": true,1119+ "os": [1120+ "linux"1121+ ],1122+ "funding": {1123+ "url": "https://opencollective.com/libvips"1124+ }1125+ },1126+ "node_modules/@img/sharp-libvips-linux-ppc64": {1127+ "version": "1.3.1",1128+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz",1129+ "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==",1130+ "cpu": [1131+ "ppc64"1132+ ],1133+ "dev": true,1134+ "libc": [1135+ "glibc"1136+ ],1137+ "license": "LGPL-3.0-or-later",1138+ "optional": true,1139+ "os": [1140+ "linux"1141+ ],1142+ "funding": {1143+ "url": "https://opencollective.com/libvips"1144+ }1145+ },1146+ "node_modules/@img/sharp-libvips-linux-riscv64": {1147+ "version": "1.3.1",1148+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz",1149+ "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==",1150+ "cpu": [1151+ "riscv64"1152+ ],1153+ "dev": true,1154+ "libc": [1155+ "glibc"1156+ ],1157+ "license": "LGPL-3.0-or-later",1158+ "optional": true,1159+ "os": [1160+ "linux"1161+ ],1162+ "funding": {1163+ "url": "https://opencollective.com/libvips"1164+ }1165+ },1166+ "node_modules/@img/sharp-libvips-linux-s390x": {1167+ "version": "1.3.1",1168+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz",1169+ "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==",1170+ "cpu": [1171+ "s390x"1172+ ],1173+ "dev": true,1174+ "libc": [1175+ "glibc"1176+ ],1177+ "license": "LGPL-3.0-or-later",1178+ "optional": true,1179+ "os": [1180+ "linux"1181+ ],1182+ "funding": {1183+ "url": "https://opencollective.com/libvips"1184+ }1185+ },1186+ "node_modules/@img/sharp-libvips-linux-x64": {1187+ "version": "1.3.1",1188+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz",1189+ "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==",1190+ "cpu": [1191+ "x64"1192+ ],1193+ "dev": true,1194+ "libc": [1195+ "glibc"1196+ ],1197+ "license": "LGPL-3.0-or-later",1198+ "optional": true,1199+ "os": [1200+ "linux"1201+ ],1202+ "funding": {1203+ "url": "https://opencollective.com/libvips"1204+ }1205+ },1206+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {1207+ "version": "1.3.1",1208+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz",1209+ "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==",1210+ "cpu": [1211+ "arm64"1212+ ],1213+ "dev": true,1214+ "libc": [1215+ "musl"1216+ ],1217+ "license": "LGPL-3.0-or-later",1218+ "optional": true,1219+ "os": [1220+ "linux"1221+ ],1222+ "funding": {1223+ "url": "https://opencollective.com/libvips"1224+ }1225+ },1226+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {1227+ "version": "1.3.1",1228+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz",1229+ "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==",1230+ "cpu": [1231+ "x64"1232+ ],1233+ "dev": true,1234+ "libc": [1235+ "musl"1236+ ],1237+ "license": "LGPL-3.0-or-later",1238+ "optional": true,1239+ "os": [1240+ "linux"1241+ ],1242+ "funding": {1243+ "url": "https://opencollective.com/libvips"1244+ }1245+ },1246+ "node_modules/@img/sharp-linux-arm": {1247+ "version": "0.35.2",1248+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz",1249+ "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==",1250+ "cpu": [1251+ "arm"1252+ ],1253+ "dev": true,1254+ "libc": [1255+ "glibc"1256+ ],1257+ "license": "Apache-2.0",1258+ "optional": true,1259+ "os": [1260+ "linux"1261+ ],1262+ "engines": {1263+ "node": ">=20.9.0"1264+ },1265+ "funding": {1266+ "url": "https://opencollective.com/libvips"1267+ },1268+ "optionalDependencies": {1269+ "@img/sharp-libvips-linux-arm": "1.3.1"1270+ }1271+ },1272+ "node_modules/@img/sharp-linux-arm64": {1273+ "version": "0.35.2",1274+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz",1275+ "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==",1276+ "cpu": [1277+ "arm64"1278+ ],1279+ "dev": true,1280+ "libc": [1281+ "glibc"1282+ ],1283+ "license": "Apache-2.0",1284+ "optional": true,1285+ "os": [1286+ "linux"1287+ ],1288+ "engines": {1289+ "node": ">=20.9.0"1290+ },1291+ "funding": {1292+ "url": "https://opencollective.com/libvips"1293+ },1294+ "optionalDependencies": {1295+ "@img/sharp-libvips-linux-arm64": "1.3.1"1296+ }1297+ },1298+ "node_modules/@img/sharp-linux-ppc64": {1299+ "version": "0.35.2",1300+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz",1301+ "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==",1302+ "cpu": [1303+ "ppc64"1304+ ],1305+ "dev": true,1306+ "libc": [1307+ "glibc"1308+ ],1309+ "license": "Apache-2.0",1310+ "optional": true,1311+ "os": [1312+ "linux"1313+ ],1314+ "engines": {1315+ "node": ">=20.9.0"1316+ },1317+ "funding": {1318+ "url": "https://opencollective.com/libvips"1319+ },1320+ "optionalDependencies": {1321+ "@img/sharp-libvips-linux-ppc64": "1.3.1"1322+ }1323+ },1324+ "node_modules/@img/sharp-linux-riscv64": {1325+ "version": "0.35.2",1326+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz",1327+ "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==",1328+ "cpu": [1329+ "riscv64"1330+ ],1331+ "dev": true,1332+ "libc": [1333+ "glibc"1334+ ],1335+ "license": "Apache-2.0",1336+ "optional": true,1337+ "os": [1338+ "linux"1339+ ],1340+ "engines": {1341+ "node": ">=20.9.0"1342+ },1343+ "funding": {1344+ "url": "https://opencollective.com/libvips"1345+ },1346+ "optionalDependencies": {1347+ "@img/sharp-libvips-linux-riscv64": "1.3.1"1348+ }1349+ },1350+ "node_modules/@img/sharp-linux-s390x": {1351+ "version": "0.35.2",1352+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz",1353+ "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==",1354+ "cpu": [1355+ "s390x"1356+ ],1357+ "dev": true,1358+ "libc": [1359+ "glibc"1360+ ],1361+ "license": "Apache-2.0",1362+ "optional": true,1363+ "os": [1364+ "linux"1365+ ],1366+ "engines": {1367+ "node": ">=20.9.0"1368+ },1369+ "funding": {1370+ "url": "https://opencollective.com/libvips"1371+ },1372+ "optionalDependencies": {1373+ "@img/sharp-libvips-linux-s390x": "1.3.1"1374+ }1375+ },1376+ "node_modules/@img/sharp-linux-x64": {1377+ "version": "0.35.2",1378+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz",1379+ "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==",1380+ "cpu": [1381+ "x64"1382+ ],1383+ "dev": true,1384+ "libc": [1385+ "glibc"1386+ ],1387+ "license": "Apache-2.0",1388+ "optional": true,1389+ "os": [1390+ "linux"1391+ ],1392+ "engines": {1393+ "node": ">=20.9.0"1394+ },1395+ "funding": {1396+ "url": "https://opencollective.com/libvips"1397+ },1398+ "optionalDependencies": {1399+ "@img/sharp-libvips-linux-x64": "1.3.1"1400+ }1401+ },1402+ "node_modules/@img/sharp-linuxmusl-arm64": {1403+ "version": "0.35.2",1404+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz",1405+ "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==",1406+ "cpu": [1407+ "arm64"1408+ ],1409+ "dev": true,1410+ "libc": [1411+ "musl"1412+ ],1413+ "license": "Apache-2.0",1414+ "optional": true,1415+ "os": [1416+ "linux"1417+ ],1418+ "engines": {1419+ "node": ">=20.9.0"1420+ },1421+ "funding": {1422+ "url": "https://opencollective.com/libvips"1423+ },1424+ "optionalDependencies": {1425+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.1"1426+ }1427+ },1428+ "node_modules/@img/sharp-linuxmusl-x64": {1429+ "version": "0.35.2",1430+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz",1431+ "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==",1432+ "cpu": [1433+ "x64"1434+ ],1435+ "dev": true,1436+ "libc": [1437+ "musl"1438+ ],1439+ "license": "Apache-2.0",1440+ "optional": true,1441+ "os": [1442+ "linux"1443+ ],1444+ "engines": {1445+ "node": ">=20.9.0"1446+ },1447+ "funding": {1448+ "url": "https://opencollective.com/libvips"1449+ },1450+ "optionalDependencies": {1451+ "@img/sharp-libvips-linuxmusl-x64": "1.3.1"1452+ }1453+ },1454+ "node_modules/@img/sharp-wasm32": {1455+ "version": "0.35.2",1456+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz",1457+ "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==",1458+ "dev": true,1459+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",1460+ "optional": true,1461+ "dependencies": {1462+ "@emnapi/runtime": "^1.11.1"1463+ },1464+ "engines": {1465+ "node": ">=20.9.0"1466+ },1467+ "funding": {1468+ "url": "https://opencollective.com/libvips"1469+ }1470+ },1471+ "node_modules/@img/sharp-webcontainers-wasm32": {1472+ "version": "0.35.2",1473+ "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz",1474+ "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==",1475+ "cpu": [1476+ "wasm32"1477+ ],1478+ "dev": true,1479+ "license": "Apache-2.0",1480+ "optional": true,1481+ "dependencies": {1482+ "@img/sharp-wasm32": "0.35.2"1483+ },1484+ "engines": {1485+ "node": ">=20.9.0"1486+ },1487+ "funding": {1488+ "url": "https://opencollective.com/libvips"1489+ }1490+ },1491+ "node_modules/@img/sharp-win32-arm64": {1492+ "version": "0.35.2",1493+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz",1494+ "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==",1495+ "cpu": [1496+ "arm64"1497+ ],1498+ "dev": true,1499+ "license": "Apache-2.0 AND LGPL-3.0-or-later",1500+ "optional": true,1501+ "os": [1502+ "win32"1503+ ],1504+ "engines": {1505+ "node": ">=20.9.0"1506+ },1507+ "funding": {1508+ "url": "https://opencollective.com/libvips"1509+ }1510+ },1511+ "node_modules/@img/sharp-win32-ia32": {1512+ "version": "0.35.2",1513+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz",1514+ "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==",1515+ "cpu": [1516+ "ia32"1517+ ],1518+ "dev": true,1519+ "license": "Apache-2.0 AND LGPL-3.0-or-later",1520+ "optional": true,1521+ "os": [1522+ "win32"1523+ ],1524+ "engines": {1525+ "node": "^20.9.0"1526+ },1527+ "funding": {1528+ "url": "https://opencollective.com/libvips"1529+ }1530+ },1531+ "node_modules/@img/sharp-win32-x64": {1532+ "version": "0.35.2",1533+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz",1534+ "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==",1535+ "cpu": [1536+ "x64"1537+ ],1538+ "dev": true,1539+ "license": "Apache-2.0 AND LGPL-3.0-or-later",1540+ "optional": true,1541+ "os": [1542+ "win32"1543+ ],1544+ "engines": {1545+ "node": ">=20.9.0"1546+ },1547+ "funding": {1548+ "url": "https://opencollective.com/libvips"1549+ }1550+ },1551+ "node_modules/@jitl/quickjs-ffi-types": {1552+ "version": "0.32.0",1553+ "resolved": "https://registry.npmjs.org/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.32.0.tgz",1554+ "integrity": "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg==",1555+ "license": "MIT"1556+ },1557+ "node_modules/@jitl/quickjs-wasmfile-debug-asyncify": {1558+ "version": "0.32.0",1559+ "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.32.0.tgz",1560+ "integrity": "sha512-EX8zbXwGqCgAE764M+qvkHtyXDi/FUoMBea0JnES7vCM3P7a2+EOZOjGv85wtZ2sJhI1oJ+nekmqpOODFDY+hw==",1561+ "license": "MIT",1562+ "dependencies": {1563+ "@jitl/quickjs-ffi-types": "0.32.0"1564+ }1565+ },1566+ "node_modules/@jitl/quickjs-wasmfile-debug-sync": {1567+ "version": "0.32.0",1568+ "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-sync/-/quickjs-wasmfile-debug-sync-0.32.0.tgz",1569+ "integrity": "sha512-LeYWrPGC1uNCTBWvibo3ZLJj0CSVNYUXvJpXMCmuQ5Sap2cCACc3uvGvYV4homHHBAzfw5akoTqMMS4YFRtw+Q==",1570+ "license": "MIT",1571+ "dependencies": {1572+ "@jitl/quickjs-ffi-types": "0.32.0"1573+ }1574+ },1575+ "node_modules/@jitl/quickjs-wasmfile-release-asyncify": {1576+ "version": "0.32.0",1577+ "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-asyncify/-/quickjs-wasmfile-release-asyncify-0.32.0.tgz",1578+ "integrity": "sha512-3oSwPfja12ICz4aIblB58cuY8JlEq5Txt8Cut4VLo+LH47QN+mzCnSgnbB03hWzg1LBcc+VyyI9UOag7a1NF+Q==",1579+ "license": "MIT",1580+ "dependencies": {1581+ "@jitl/quickjs-ffi-types": "0.32.0"1582+ }1583+ },1584+ "node_modules/@jitl/quickjs-wasmfile-release-sync": {1585+ "version": "0.32.0",1586+ "resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-release-sync/-/quickjs-wasmfile-release-sync-0.32.0.tgz",1587+ "integrity": "sha512-BKNDI/TPBfGlLNGYpLrhcDGXmIk4xHm4MRAisOBnOzpXVn9HZWsfmMAc9WMBrAHjvvds6HOikKeaOBKdPdpVrg==",1588+ "license": "MIT",1589+ "dependencies": {1590+ "@jitl/quickjs-ffi-types": "0.32.0"1591+ }1592+ },1593+ "node_modules/@jridgewell/gen-mapping": {1594+ "version": "0.3.13",1595+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",1596+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",1597+ "license": "MIT",1598+ "dependencies": {1599+ "@jridgewell/sourcemap-codec": "^1.5.0",1600+ "@jridgewell/trace-mapping": "^0.3.24"1601+ }1602+ },1603+ "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": {1604+ "version": "0.3.31",1605+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",1606+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",1607+ "license": "MIT",1608+ "dependencies": {1609+ "@jridgewell/resolve-uri": "^3.1.0",1610+ "@jridgewell/sourcemap-codec": "^1.4.14"1611+ }1612+ },1613+ "node_modules/@jridgewell/resolve-uri": {1614+ "version": "3.1.2",1615+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",1616+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",1617+ "license": "MIT",1618+ "engines": {1619+ "node": ">=6.0.0"1620+ }1621+ },1622+ "node_modules/@jridgewell/sourcemap-codec": {1623+ "version": "1.5.5",1624+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",1625+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",1626+ "license": "MIT"1627+ },1628+ "node_modules/@jridgewell/trace-mapping": {1629+ "version": "0.3.9",1630+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",1631+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",1632+ "dev": true,1633+ "license": "MIT",1634+ "dependencies": {1635+ "@jridgewell/resolve-uri": "^3.0.3",1636+ "@jridgewell/sourcemap-codec": "^1.4.10"1637+ }1638+ },1639+ "node_modules/@modelcontextprotocol/client": {1640+ "version": "2.0.0",1641+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0.tgz",1642+ "integrity": "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==",1643+ "license": "MIT",1644+ "peer": true,1645+ "dependencies": {1646+ "@modelcontextprotocol/core": "2.0.0",1647+ "cross-spawn": "^7.0.5",1648+ "eventsource": "^3.0.2",1649+ "eventsource-parser": "^3.0.0",1650+ "jose": "^6.1.3",1651+ "pkce-challenge": "^5.0.0",1652+ "zod": "^4.2.0"1653+ },1654+ "engines": {1655+ "node": ">=20"1656+ }1657+ },1658+ "node_modules/@modelcontextprotocol/core": {1659+ "version": "2.0.0",1660+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz",1661+ "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==",1662+ "license": "MIT",1663+ "dependencies": {1664+ "zod": "^4.2.0"1665+ },1666+ "engines": {1667+ "node": ">=20"1668+ }1669+ },1670+ "node_modules/@modelcontextprotocol/sdk": {1671+ "version": "1.30.0",1672+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",1673+ "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==",1674+ "license": "MIT",1675+ "peer": true,1676+ "dependencies": {1677+ "@hono/node-server": "^1.19.9 || ^2.0.5",1678+ "ajv": "^8.17.1",1679+ "ajv-formats": "^3.0.1",1680+ "content-type": "^1.0.5",1681+ "cors": "^2.8.5",1682+ "cross-spawn": "^7.0.5",1683+ "eventsource": "^3.0.2",1684+ "eventsource-parser": "^3.0.0",1685+ "express": "^5.2.1",1686+ "express-rate-limit": "^8.2.1",1687+ "hono": "^4.11.4",1688+ "jose": "^6.1.3",1689+ "json-schema-typed": "^8.0.2",1690+ "pkce-challenge": "^5.0.0",1691+ "raw-body": "^3.0.0",1692+ "zod": "^3.25 || ^4.0",1693+ "zod-to-json-schema": "^3.25.1"1694+ },1695+ "engines": {1696+ "node": ">=18"1697+ },1698+ "peerDependencies": {1699+ "@cfworker/json-schema": "^4.1.1",1700+ "zod": "^3.25 || ^4.0"1701+ },1702+ "peerDependenciesMeta": {1703+ "@cfworker/json-schema": {1704+ "optional": true1705+ },1706+ "zod": {1707+ "optional": false1708+ }1709+ }1710+ },1711+ "node_modules/@modelcontextprotocol/server": {1712+ "version": "2.0.0",1713+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz",1714+ "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==",1715+ "license": "MIT",1716+ "dependencies": {1717+ "@modelcontextprotocol/core": "2.0.0",1718+ "zod": "^4.2.0"1719+ },1720+ "engines": {1721+ "node": ">=20"1722+ }1723+ },1724+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {1725+ "version": "1.5.1",1726+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",1727+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",1728+ "cpu": [1729+ "x64"1730+ ],1731+ "dev": true,1732+ "libc": [1733+ "glibc"1734+ ],1735+ "license": "MIT",1736+ "optional": true,1737+ "os": [1738+ "linux"1739+ ],1740+ "engines": {1741+ "node": "^22.20 || ^24.12 || >=25"1742+ }1743+ },1744+ "node_modules/@oxc-project/types": {1745+ "version": "0.144.0",1746+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz",1747+ "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==",1748+ "license": "MIT",1749+ "peer": true,1750+ "funding": {1751+ "url": "https://github.com/sponsors/Boshen"1752+ }1753+ },1754+ "node_modules/@poppinss/colors": {1755+ "version": "4.1.6",1756+ "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",1757+ "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==",1758+ "dev": true,1759+ "license": "MIT",1760+ "dependencies": {1761+ "kleur": "^4.1.5"1762+ }1763+ },1764+ "node_modules/@poppinss/dumper": {1765+ "version": "0.6.5",1766+ "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz",1767+ "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==",1768+ "dev": true,1769+ "license": "MIT",1770+ "dependencies": {1771+ "@poppinss/colors": "^4.1.5",1772+ "@sindresorhus/is": "^7.0.2",1773+ "supports-color": "^10.0.0"1774+ }1775+ },1776+ "node_modules/@poppinss/exception": {1777+ "version": "1.2.3",1778+ "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz",1779+ "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==",1780+ "dev": true,1781+ "license": "MIT"1782+ },1783+ "node_modules/@rolldown/binding-android-arm64": {1784+ "version": "1.2.4",1785+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",1786+ "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==",1787+ "cpu": [1788+ "arm64"1789+ ],1790+ "license": "MIT",1791+ "optional": true,1792+ "os": [1793+ "android"1794+ ],1795+ "peer": true,1796+ "engines": {1797+ "node": "^20.19.0 || >=22.12.0"1798+ }1799+ },1800+ "node_modules/@rolldown/binding-darwin-arm64": {1801+ "version": "1.2.4",1802+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz",1803+ "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==",1804+ "cpu": [1805+ "arm64"1806+ ],1807+ "license": "MIT",1808+ "optional": true,1809+ "os": [1810+ "darwin"1811+ ],1812+ "peer": true,1813+ "engines": {1814+ "node": "^20.19.0 || >=22.12.0"1815+ }1816+ },1817+ "node_modules/@rolldown/binding-darwin-x64": {1818+ "version": "1.2.4",1819+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz",1820+ "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==",1821+ "cpu": [1822+ "x64"1823+ ],1824+ "license": "MIT",1825+ "optional": true,1826+ "os": [1827+ "darwin"1828+ ],1829+ "peer": true,1830+ "engines": {1831+ "node": "^20.19.0 || >=22.12.0"1832+ }1833+ },1834+ "node_modules/@rolldown/binding-freebsd-x64": {1835+ "version": "1.2.4",1836+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz",1837+ "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==",1838+ "cpu": [1839+ "x64"1840+ ],1841+ "license": "MIT",1842+ "optional": true,1843+ "os": [1844+ "freebsd"1845+ ],1846+ "peer": true,1847+ "engines": {1848+ "node": "^20.19.0 || >=22.12.0"1849+ }1850+ },1851+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {1852+ "version": "1.2.4",1853+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz",1854+ "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==",1855+ "cpu": [1856+ "arm"1857+ ],1858+ "license": "MIT",1859+ "optional": true,1860+ "os": [1861+ "linux"1862+ ],1863+ "peer": true,1864+ "engines": {1865+ "node": "^20.19.0 || >=22.12.0"1866+ }1867+ },1868+ "node_modules/@rolldown/binding-linux-arm64-gnu": {1869+ "version": "1.2.4",1870+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz",1871+ "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==",1872+ "cpu": [1873+ "arm64"1874+ ],1875+ "libc": [1876+ "glibc"1877+ ],1878+ "license": "MIT",1879+ "optional": true,1880+ "os": [1881+ "linux"1882+ ],1883+ "peer": true,1884+ "engines": {1885+ "node": "^20.19.0 || >=22.12.0"1886+ }1887+ },1888+ "node_modules/@rolldown/binding-linux-arm64-musl": {1889+ "version": "1.2.4",1890+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz",1891+ "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==",1892+ "cpu": [1893+ "arm64"1894+ ],1895+ "libc": [1896+ "musl"1897+ ],1898+ "license": "MIT",1899+ "optional": true,1900+ "os": [1901+ "linux"1902+ ],1903+ "peer": true,1904+ "engines": {1905+ "node": "^20.19.0 || >=22.12.0"1906+ }1907+ },1908+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {1909+ "version": "1.2.4",1910+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz",1911+ "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==",1912+ "cpu": [1913+ "ppc64"1914+ ],1915+ "libc": [1916+ "glibc"1917+ ],1918+ "license": "MIT",1919+ "optional": true,1920+ "os": [1921+ "linux"1922+ ],1923+ "peer": true,1924+ "engines": {1925+ "node": "^20.19.0 || >=22.12.0"1926+ }1927+ },1928+ "node_modules/@rolldown/binding-linux-s390x-gnu": {1929+ "version": "1.2.4",1930+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz",1931+ "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==",1932+ "cpu": [1933+ "s390x"1934+ ],1935+ "libc": [1936+ "glibc"1937+ ],1938+ "license": "MIT",1939+ "optional": true,1940+ "os": [1941+ "linux"1942+ ],1943+ "peer": true,1944+ "engines": {1945+ "node": "^20.19.0 || >=22.12.0"1946+ }1947+ },1948+ "node_modules/@rolldown/binding-linux-x64-gnu": {1949+ "version": "1.2.4",1950+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz",1951+ "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==",1952+ "cpu": [1953+ "x64"1954+ ],1955+ "libc": [1956+ "glibc"1957+ ],1958+ "license": "MIT",1959+ "optional": true,1960+ "os": [1961+ "linux"1962+ ],1963+ "peer": true,1964+ "engines": {1965+ "node": "^20.19.0 || >=22.12.0"1966+ }1967+ },1968+ "node_modules/@rolldown/binding-linux-x64-musl": {1969+ "version": "1.2.4",1970+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz",1971+ "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==",1972+ "cpu": [1973+ "x64"1974+ ],1975+ "libc": [1976+ "musl"1977+ ],1978+ "license": "MIT",1979+ "optional": true,1980+ "os": [1981+ "linux"1982+ ],1983+ "peer": true,1984+ "engines": {1985+ "node": "^20.19.0 || >=22.12.0"1986+ }1987+ },1988+ "node_modules/@rolldown/binding-openharmony-arm64": {1989+ "version": "1.2.4",1990+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz",1991+ "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==",1992+ "cpu": [1993+ "arm64"1994+ ],1995+ "license": "MIT",1996+ "optional": true,1997+ "os": [1998+ "openharmony"1999+ ],2000+ "peer": true,2001+ "engines": {2002+ "node": "^20.19.0 || >=22.12.0"2003+ }2004+ },2005+ "node_modules/@rolldown/binding-win32-arm64-msvc": {2006+ "version": "1.2.4",2007+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz",2008+ "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==",2009+ "cpu": [2010+ "arm64"2011+ ],2012+ "license": "MIT",2013+ "optional": true,2014+ "os": [2015+ "win32"2016+ ],2017+ "peer": true,2018+ "engines": {2019+ "node": "^20.19.0 || >=22.12.0"2020+ }2021+ },2022+ "node_modules/@rolldown/binding-win32-x64-msvc": {2023+ "version": "1.2.4",2024+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz",2025+ "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==",2026+ "cpu": [2027+ "x64"2028+ ],2029+ "license": "MIT",2030+ "optional": true,2031+ "os": [2032+ "win32"2033+ ],2034+ "peer": true,2035+ "engines": {2036+ "node": "^20.19.0 || >=22.12.0"2037+ }2038+ },2039+ "node_modules/@rolldown/plugin-babel": {2040+ "version": "0.2.3",2041+ "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.3.tgz",2042+ "integrity": "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==",2043+ "license": "MIT",2044+ "dependencies": {2045+ "picomatch": "^4.0.4"2046+ },2047+ "engines": {2048+ "node": ">=22.12.0 || ^24.0.0"2049+ },2050+ "peerDependencies": {2051+ "@babel/core": "^7.29.0 || ^8.0.0-rc.1",2052+ "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1",2053+ "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1",2054+ "rolldown": "^1.0.0-rc.5",2055+ "vite": "^8.0.0"2056+ },2057+ "peerDependenciesMeta": {2058+ "@babel/plugin-transform-runtime": {2059+ "optional": true2060+ },2061+ "@babel/runtime": {2062+ "optional": true2063+ },2064+ "vite": {2065+ "optional": true2066+ }2067+ }2068+ },2069+ "node_modules/@rolldown/pluginutils": {2070+ "version": "1.0.1",2071+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",2072+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",2073+ "license": "MIT",2074+ "peer": true2075+ },2076+ "node_modules/@rollup/rollup-android-arm-eabi": {2077+ "version": "4.62.4",2078+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz",2079+ "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==",2080+ "cpu": [2081+ "arm"2082+ ],2083+ "dev": true,2084+ "license": "MIT",2085+ "optional": true,2086+ "os": [2087+ "android"2088+ ]2089+ },2090+ "node_modules/@rollup/rollup-android-arm64": {2091+ "version": "4.62.4",2092+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz",2093+ "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==",2094+ "cpu": [2095+ "arm64"2096+ ],2097+ "dev": true,2098+ "license": "MIT",2099+ "optional": true,2100+ "os": [2101+ "android"2102+ ]2103+ },2104+ "node_modules/@rollup/rollup-darwin-arm64": {2105+ "version": "4.62.4",2106+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz",2107+ "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==",2108+ "cpu": [2109+ "arm64"2110+ ],2111+ "dev": true,2112+ "license": "MIT",2113+ "optional": true,2114+ "os": [2115+ "darwin"2116+ ]2117+ },2118+ "node_modules/@rollup/rollup-darwin-x64": {2119+ "version": "4.62.4",2120+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz",2121+ "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==",2122+ "cpu": [2123+ "x64"2124+ ],2125+ "dev": true,2126+ "license": "MIT",2127+ "optional": true,2128+ "os": [2129+ "darwin"2130+ ]2131+ },2132+ "node_modules/@rollup/rollup-freebsd-arm64": {2133+ "version": "4.62.4",2134+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz",2135+ "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==",2136+ "cpu": [2137+ "arm64"2138+ ],2139+ "dev": true,2140+ "license": "MIT",2141+ "optional": true,2142+ "os": [2143+ "freebsd"2144+ ]2145+ },2146+ "node_modules/@rollup/rollup-freebsd-x64": {2147+ "version": "4.62.4",2148+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz",2149+ "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==",2150+ "cpu": [2151+ "x64"2152+ ],2153+ "dev": true,2154+ "license": "MIT",2155+ "optional": true,2156+ "os": [2157+ "freebsd"2158+ ]2159+ },2160+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {2161+ "version": "4.62.4",2162+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz",2163+ "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==",2164+ "cpu": [2165+ "arm"2166+ ],2167+ "dev": true,2168+ "libc": [2169+ "glibc"2170+ ],2171+ "license": "MIT",2172+ "optional": true,2173+ "os": [2174+ "linux"2175+ ]2176+ },2177+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {2178+ "version": "4.62.4",2179+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz",2180+ "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==",2181+ "cpu": [2182+ "arm"2183+ ],2184+ "dev": true,2185+ "libc": [2186+ "musl"2187+ ],2188+ "license": "MIT",2189+ "optional": true,2190+ "os": [2191+ "linux"2192+ ]2193+ },2194+ "node_modules/@rollup/rollup-linux-arm64-gnu": {2195+ "version": "4.62.4",2196+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz",2197+ "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==",2198+ "cpu": [2199+ "arm64"2200+ ],2201+ "dev": true,2202+ "libc": [2203+ "glibc"2204+ ],2205+ "license": "MIT",2206+ "optional": true,2207+ "os": [2208+ "linux"2209+ ]2210+ },2211+ "node_modules/@rollup/rollup-linux-arm64-musl": {2212+ "version": "4.62.4",2213+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz",2214+ "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==",2215+ "cpu": [2216+ "arm64"2217+ ],2218+ "dev": true,2219+ "libc": [2220+ "musl"2221+ ],2222+ "license": "MIT",2223+ "optional": true,2224+ "os": [2225+ "linux"2226+ ]2227+ },2228+ "node_modules/@rollup/rollup-linux-loong64-gnu": {2229+ "version": "4.62.4",2230+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz",2231+ "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==",2232+ "cpu": [2233+ "loong64"2234+ ],2235+ "dev": true,2236+ "libc": [2237+ "glibc"2238+ ],2239+ "license": "MIT",2240+ "optional": true,2241+ "os": [2242+ "linux"2243+ ]2244+ },2245+ "node_modules/@rollup/rollup-linux-loong64-musl": {2246+ "version": "4.62.4",2247+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz",2248+ "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==",2249+ "cpu": [2250+ "loong64"2251+ ],2252+ "dev": true,2253+ "libc": [2254+ "musl"2255+ ],2256+ "license": "MIT",2257+ "optional": true,2258+ "os": [2259+ "linux"2260+ ]2261+ },2262+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {2263+ "version": "4.62.4",2264+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz",2265+ "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==",2266+ "cpu": [2267+ "ppc64"2268+ ],2269+ "dev": true,2270+ "libc": [2271+ "glibc"2272+ ],2273+ "license": "MIT",2274+ "optional": true,2275+ "os": [2276+ "linux"2277+ ]2278+ },2279+ "node_modules/@rollup/rollup-linux-ppc64-musl": {2280+ "version": "4.62.4",2281+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz",2282+ "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==",2283+ "cpu": [2284+ "ppc64"2285+ ],2286+ "dev": true,2287+ "libc": [2288+ "musl"2289+ ],2290+ "license": "MIT",2291+ "optional": true,2292+ "os": [2293+ "linux"2294+ ]2295+ },2296+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {2297+ "version": "4.62.4",2298+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz",2299+ "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==",2300+ "cpu": [2301+ "riscv64"2302+ ],2303+ "dev": true,2304+ "libc": [2305+ "glibc"2306+ ],2307+ "license": "MIT",2308+ "optional": true,2309+ "os": [2310+ "linux"2311+ ]2312+ },2313+ "node_modules/@rollup/rollup-linux-riscv64-musl": {2314+ "version": "4.62.4",2315+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz",2316+ "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==",2317+ "cpu": [2318+ "riscv64"2319+ ],2320+ "dev": true,2321+ "libc": [2322+ "musl"2323+ ],2324+ "license": "MIT",2325+ "optional": true,2326+ "os": [2327+ "linux"2328+ ]2329+ },2330+ "node_modules/@rollup/rollup-linux-s390x-gnu": {2331+ "version": "4.62.4",2332+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz",2333+ "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==",2334+ "cpu": [2335+ "s390x"2336+ ],2337+ "dev": true,2338+ "libc": [2339+ "glibc"2340+ ],2341+ "license": "MIT",2342+ "optional": true,2343+ "os": [2344+ "linux"2345+ ]2346+ },2347+ "node_modules/@rollup/rollup-linux-x64-gnu": {2348+ "version": "4.62.4",2349+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz",2350+ "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==",2351+ "cpu": [2352+ "x64"2353+ ],2354+ "dev": true,2355+ "libc": [2356+ "glibc"2357+ ],2358+ "license": "MIT",2359+ "optional": true,2360+ "os": [2361+ "linux"2362+ ]2363+ },2364+ "node_modules/@rollup/rollup-linux-x64-musl": {2365+ "version": "4.62.4",2366+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz",2367+ "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==",2368+ "cpu": [2369+ "x64"2370+ ],2371+ "dev": true,2372+ "libc": [2373+ "musl"2374+ ],2375+ "license": "MIT",2376+ "optional": true,2377+ "os": [2378+ "linux"2379+ ]2380+ },2381+ "node_modules/@rollup/rollup-openbsd-x64": {2382+ "version": "4.62.4",2383+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz",2384+ "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==",2385+ "cpu": [2386+ "x64"2387+ ],2388+ "dev": true,2389+ "license": "MIT",2390+ "optional": true,2391+ "os": [2392+ "openbsd"2393+ ]2394+ },2395+ "node_modules/@rollup/rollup-openharmony-arm64": {2396+ "version": "4.62.4",2397+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz",2398+ "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==",2399+ "cpu": [2400+ "arm64"2401+ ],2402+ "dev": true,2403+ "license": "MIT",2404+ "optional": true,2405+ "os": [2406+ "openharmony"2407+ ]2408+ },2409+ "node_modules/@rollup/rollup-win32-arm64-msvc": {2410+ "version": "4.62.4",2411+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz",2412+ "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==",2413+ "cpu": [2414+ "arm64"2415+ ],2416+ "dev": true,2417+ "license": "MIT",2418+ "optional": true,2419+ "os": [2420+ "win32"2421+ ]2422+ },2423+ "node_modules/@rollup/rollup-win32-ia32-msvc": {2424+ "version": "4.62.4",2425+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz",2426+ "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==",2427+ "cpu": [2428+ "ia32"2429+ ],2430+ "dev": true,2431+ "license": "MIT",2432+ "optional": true,2433+ "os": [2434+ "win32"2435+ ]2436+ },2437+ "node_modules/@rollup/rollup-win32-x64-gnu": {2438+ "version": "4.62.4",2439+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz",2440+ "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==",2441+ "cpu": [2442+ "x64"2443+ ],2444+ "dev": true,2445+ "license": "MIT",2446+ "optional": true,2447+ "os": [2448+ "win32"2449+ ]2450+ },2451+ "node_modules/@rollup/rollup-win32-x64-msvc": {2452+ "version": "4.62.4",2453+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz",2454+ "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==",2455+ "cpu": [2456+ "x64"2457+ ],2458+ "dev": true,2459+ "license": "MIT",2460+ "optional": true,2461+ "os": [2462+ "win32"2463+ ]2464+ },2465+ "node_modules/@sindresorhus/is": {2466+ "version": "7.2.0",2467+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz",2468+ "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==",2469+ "dev": true,2470+ "license": "MIT",2471+ "engines": {2472+ "node": ">=18"2473+ },2474+ "funding": {2475+ "url": "https://github.com/sindresorhus/is?sponsor=1"2476+ }2477+ },2478+ "node_modules/@speed-highlight/core": {2479+ "version": "1.2.24",2480+ "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz",2481+ "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==",2482+ "dev": true,2483+ "license": "CC0-1.0"2484+ },2485+ "node_modules/@tribes/engine": {2486+ "resolved": "packages/engine",2487+ "link": true2488+ },2489+ "node_modules/@tribes/protocol": {2490+ "resolved": "packages/protocol",2491+ "link": true2492+ },2493+ "node_modules/@tribes/script-host": {2494+ "resolved": "packages/script-host",2495+ "link": true2496+ },2497+ "node_modules/@types/chai": {2498+ "version": "5.2.3",2499+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",2500+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",2501+ "dev": true,2502+ "license": "MIT",2503+ "dependencies": {2504+ "@types/deep-eql": "*",2505+ "assertion-error": "^2.0.1"2506+ }2507+ },2508+ "node_modules/@types/deep-eql": {2509+ "version": "4.0.2",2510+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",2511+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",2512+ "dev": true,2513+ "license": "MIT"2514+ },2515+ "node_modules/@types/estree": {2516+ "version": "1.0.9",2517+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",2518+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",2519+ "dev": true,2520+ "license": "MIT"2521+ },2522+ "node_modules/@types/gensync": {2523+ "version": "1.0.5",2524+ "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz",2525+ "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==",2526+ "license": "MIT",2527+ "peer": true2528+ },2529+ "node_modules/@types/jsesc": {2530+ "version": "2.5.1",2531+ "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",2532+ "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==",2533+ "license": "MIT"2534+ },2535+ "node_modules/@vitest/expect": {2536+ "version": "3.2.7",2537+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",2538+ "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==",2539+ "dev": true,2540+ "license": "MIT",2541+ "dependencies": {2542+ "@types/chai": "^5.2.2",2543+ "@vitest/spy": "3.2.7",2544+ "@vitest/utils": "3.2.7",2545+ "chai": "^5.2.0",2546+ "tinyrainbow": "^2.0.0"2547+ },2548+ "funding": {2549+ "url": "https://opencollective.com/vitest"2550+ }2551+ },2552+ "node_modules/@vitest/pretty-format": {2553+ "version": "3.2.7",2554+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz",2555+ "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==",2556+ "dev": true,2557+ "license": "MIT",2558+ "dependencies": {2559+ "tinyrainbow": "^2.0.0"2560+ },2561+ "funding": {2562+ "url": "https://opencollective.com/vitest"2563+ }2564+ },2565+ "node_modules/@vitest/runner": {2566+ "version": "3.2.7",2567+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz",2568+ "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==",2569+ "dev": true,2570+ "license": "MIT",2571+ "dependencies": {2572+ "@vitest/utils": "3.2.7",2573+ "pathe": "^2.0.3",2574+ "strip-literal": "^3.0.0"2575+ },2576+ "funding": {2577+ "url": "https://opencollective.com/vitest"2578+ }2579+ },2580+ "node_modules/@vitest/snapshot": {2581+ "version": "3.2.7",2582+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz",2583+ "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==",2584+ "dev": true,2585+ "license": "MIT",2586+ "dependencies": {2587+ "@vitest/pretty-format": "3.2.7",2588+ "magic-string": "^0.30.17",2589+ "pathe": "^2.0.3"2590+ },2591+ "funding": {2592+ "url": "https://opencollective.com/vitest"2593+ }2594+ },2595+ "node_modules/@vitest/spy": {2596+ "version": "3.2.7",2597+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz",2598+ "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==",2599+ "dev": true,2600+ "license": "MIT",2601+ "dependencies": {2602+ "tinyspy": "^4.0.3"2603+ },2604+ "funding": {2605+ "url": "https://opencollective.com/vitest"2606+ }2607+ },2608+ "node_modules/@vitest/utils": {2609+ "version": "3.2.7",2610+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz",2611+ "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==",2612+ "dev": true,2613+ "license": "MIT",2614+ "dependencies": {2615+ "@vitest/pretty-format": "3.2.7",2616+ "loupe": "^3.1.4",2617+ "tinyrainbow": "^2.0.0"2618+ },2619+ "funding": {2620+ "url": "https://opencollective.com/vitest"2621+ }2622+ },2623+ "node_modules/accepts": {2624+ "version": "2.0.0",2625+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",2626+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",2627+ "license": "MIT",2628+ "peer": true,2629+ "dependencies": {2630+ "mime-types": "^3.0.0",2631+ "negotiator": "^1.0.0"2632+ },2633+ "engines": {2634+ "node": ">= 0.6"2635+ }2636+ },2637+ "node_modules/agents": {2638+ "version": "0.20.1",2639+ "resolved": "https://registry.npmjs.org/agents/-/agents-0.20.1.tgz",2640+ "integrity": "sha512-HQRYMeZpD3k8djYBH7atRPojZMee3NvmXkzsmMWXfdHZ94vMljmWqSsD1XZd70LovHyQrw6/R81AZZIsRiFM6Q==",2641+ "license": "MIT",2642+ "dependencies": {2643+ "@babel/plugin-proposal-decorators": "^8.0.2",2644+ "@cfworker/json-schema": "^4.1.1",2645+ "@rolldown/plugin-babel": "^0.2.3",2646+ "cron-schedule": "^6.0.0",2647+ "esbuild": "^0.28.1",2648+ "mimetext": "^3.0.28",2649+ "nanoid": "^5.1.16",2650+ "partyserver": "^0.5.8",2651+ "partysocket": "1.3.0",2652+ "yaml": "^2.9.0",2653+ "yargs": "^18.0.0"2654+ },2655+ "bin": {2656+ "agents": "dist/cli/index.js"2657+ },2658+ "peerDependencies": {2659+ "@ai-sdk/react": "^3.0.0 || ^4.0.0",2660+ "@cloudflare/codemode": ">=0.5.0",2661+ "@modelcontextprotocol/client": "2.0.0",2662+ "@modelcontextprotocol/sdk": "1.30.0",2663+ "@modelcontextprotocol/server": "2.0.0",2664+ "@tanstack/ai": ">=0.10.2 <1.0.0",2665+ "@x402/core": "^2.0.0",2666+ "@x402/evm": "^2.0.0",2667+ "ai": "^6.0.0 || ^7.0.0",2668+ "chat": "^4.29.0",2669+ "just-bash": "^3.0.0",2670+ "react": "^19.0.0",2671+ "vite": ">=6.0.0 <9.0.0",2672+ "zod": "^4.0.0"2673+ },2674+ "peerDependenciesMeta": {2675+ "@ai-sdk/react": {2676+ "optional": true2677+ },2678+ "@cloudflare/codemode": {2679+ "optional": true2680+ },2681+ "@tanstack/ai": {2682+ "optional": true2683+ },2684+ "@x402/core": {2685+ "optional": true2686+ },2687+ "@x402/evm": {2688+ "optional": true2689+ },2690+ "ai": {2691+ "optional": true2692+ },2693+ "chat": {2694+ "optional": true2695+ },2696+ "just-bash": {2697+ "optional": true2698+ },2699+ "vite": {2700+ "optional": true2701+ }2702+ }2703+ },2704+ "node_modules/agents/node_modules/nanoid": {2705+ "version": "5.1.16",2706+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz",2707+ "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==",2708+ "funding": [2709+ {2710+ "type": "github",2711+ "url": "https://github.com/sponsors/ai"2712+ }2713+ ],2714+ "license": "MIT",2715+ "bin": {2716+ "nanoid": "bin/nanoid.js"2717+ },2718+ "engines": {2719+ "node": "^18 || >=20"2720+ }2721+ },2722+ "node_modules/ajv": {2723+ "version": "8.20.0",2724+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",2725+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",2726+ "license": "MIT",2727+ "peer": true,2728+ "dependencies": {2729+ "fast-deep-equal": "^3.1.3",2730+ "fast-uri": "^3.0.1",2731+ "json-schema-traverse": "^1.0.0",2732+ "require-from-string": "^2.0.2"2733+ },2734+ "funding": {2735+ "type": "github",2736+ "url": "https://github.com/sponsors/epoberezkin"2737+ }2738+ },2739+ "node_modules/ajv-formats": {2740+ "version": "3.0.1",2741+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",2742+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",2743+ "license": "MIT",2744+ "peer": true,2745+ "dependencies": {2746+ "ajv": "^8.0.0"2747+ },2748+ "peerDependencies": {2749+ "ajv": "^8.0.0"2750+ },2751+ "peerDependenciesMeta": {2752+ "ajv": {2753+ "optional": true2754+ }2755+ }2756+ },2757+ "node_modules/ansi-regex": {2758+ "version": "6.3.0",2759+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz",2760+ "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==",2761+ "license": "MIT",2762+ "engines": {2763+ "node": ">=12"2764+ },2765+ "funding": {2766+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"2767+ }2768+ },2769+ "node_modules/ansi-styles": {2770+ "version": "6.2.3",2771+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",2772+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",2773+ "license": "MIT",2774+ "engines": {2775+ "node": ">=12"2776+ },2777+ "funding": {2778+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"2779+ }2780+ },2781+ "node_modules/assertion-error": {2782+ "version": "2.0.1",2783+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",2784+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",2785+ "dev": true,2786+ "license": "MIT",2787+ "engines": {2788+ "node": ">=12"2789+ }2790+ },2791+ "node_modules/baseline-browser-mapping": {2792+ "version": "2.11.14",2793+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz",2794+ "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==",2795+ "license": "Apache-2.0",2796+ "peer": true,2797+ "bin": {2798+ "baseline-browser-mapping": "dist/cli.cjs"2799+ },2800+ "engines": {2801+ "node": ">=6.0.0"2802+ }2803+ },2804+ "node_modules/blake3-wasm": {2805+ "version": "2.1.5",2806+ "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",2807+ "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",2808+ "dev": true,2809+ "license": "MIT"2810+ },2811+ "node_modules/body-parser": {2812+ "version": "2.3.0",2813+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",2814+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",2815+ "license": "MIT",2816+ "peer": true,2817+ "dependencies": {2818+ "bytes": "^3.1.2",2819+ "content-type": "^2.0.0",2820+ "debug": "^4.4.3",2821+ "http-errors": "^2.0.1",2822+ "iconv-lite": "^0.7.2",2823+ "on-finished": "^2.4.1",2824+ "qs": "^6.15.2",2825+ "raw-body": "^3.0.2",2826+ "type-is": "^2.1.0"2827+ },2828+ "engines": {2829+ "node": ">=18"2830+ },2831+ "funding": {2832+ "type": "opencollective",2833+ "url": "https://opencollective.com/express"2834+ }2835+ },2836+ "node_modules/body-parser/node_modules/content-type": {2837+ "version": "2.1.0",2838+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",2839+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",2840+ "license": "MIT",2841+ "peer": true,2842+ "engines": {2843+ "node": ">=18"2844+ },2845+ "funding": {2846+ "type": "opencollective",2847+ "url": "https://opencollective.com/express"2848+ }2849+ },2850+ "node_modules/browserslist": {2851+ "version": "4.28.8",2852+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",2853+ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",2854+ "funding": [2855+ {2856+ "type": "opencollective",2857+ "url": "https://opencollective.com/browserslist"2858+ },2859+ {2860+ "type": "tidelift",2861+ "url": "https://tidelift.com/funding/github/npm/browserslist"2862+ },2863+ {2864+ "type": "github",2865+ "url": "https://github.com/sponsors/ai"2866+ }2867+ ],2868+ "license": "MIT",2869+ "peer": true,2870+ "dependencies": {2871+ "baseline-browser-mapping": "^2.11.12",2872+ "caniuse-lite": "^1.0.30001809",2873+ "electron-to-chromium": "^1.5.402",2874+ "node-releases": "^2.0.53",2875+ "update-browserslist-db": "^1.3.0"2876+ },2877+ "bin": {2878+ "browserslist": "cli.js"2879+ },2880+ "engines": {2881+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"2882+ }2883+ },2884+ "node_modules/bytes": {2885+ "version": "3.1.2",2886+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",2887+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",2888+ "license": "MIT",2889+ "peer": true,2890+ "engines": {2891+ "node": ">= 0.8"2892+ }2893+ },2894+ "node_modules/cac": {2895+ "version": "6.7.14",2896+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",2897+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",2898+ "dev": true,2899+ "license": "MIT",2900+ "engines": {2901+ "node": ">=8"2902+ }2903+ },2904+ "node_modules/call-bind-apply-helpers": {2905+ "version": "1.0.2",2906+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",2907+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",2908+ "license": "MIT",2909+ "peer": true,2910+ "dependencies": {2911+ "es-errors": "^1.3.0",2912+ "function-bind": "^1.1.2"2913+ },2914+ "engines": {2915+ "node": ">= 0.4"2916+ }2917+ },2918+ "node_modules/call-bound": {2919+ "version": "1.0.4",2920+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",2921+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",2922+ "license": "MIT",2923+ "peer": true,2924+ "dependencies": {2925+ "call-bind-apply-helpers": "^1.0.2",2926+ "get-intrinsic": "^1.3.0"2927+ },2928+ "engines": {2929+ "node": ">= 0.4"2930+ },2931+ "funding": {2932+ "url": "https://github.com/sponsors/ljharb"2933+ }2934+ },2935+ "node_modules/caniuse-lite": {2936+ "version": "1.0.30001809",2937+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",2938+ "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",2939+ "funding": [2940+ {2941+ "type": "opencollective",2942+ "url": "https://opencollective.com/browserslist"2943+ },2944+ {2945+ "type": "tidelift",2946+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"2947+ },2948+ {2949+ "type": "github",2950+ "url": "https://github.com/sponsors/ai"2951+ }2952+ ],2953+ "license": "CC-BY-4.0",2954+ "peer": true2955+ },2956+ "node_modules/chai": {2957+ "version": "5.3.3",2958+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",2959+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",2960+ "dev": true,2961+ "license": "MIT",2962+ "dependencies": {2963+ "assertion-error": "^2.0.1",2964+ "check-error": "^2.1.1",2965+ "deep-eql": "^5.0.1",2966+ "loupe": "^3.1.0",2967+ "pathval": "^2.0.0"2968+ },2969+ "engines": {2970+ "node": ">=18"2971+ }2972+ },2973+ "node_modules/check-error": {2974+ "version": "2.1.3",2975+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",2976+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",2977+ "dev": true,2978+ "license": "MIT",2979+ "engines": {2980+ "node": ">= 16"2981+ }2982+ },2983+ "node_modules/cliui": {2984+ "version": "9.0.1",2985+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",2986+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",2987+ "license": "ISC",2988+ "dependencies": {2989+ "string-width": "^7.2.0",2990+ "strip-ansi": "^7.1.0",2991+ "wrap-ansi": "^9.0.0"2992+ },2993+ "engines": {2994+ "node": ">=20"2995+ }2996+ },2997+ "node_modules/cliui/node_modules/string-width": {2998+ "version": "7.2.0",2999+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",3000+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
diff truncated
A package.json +24 -0
A packages/engine/README.md +74 -0
@@ -0,0 +1,74 @@
1+# @tribes/engine2+3+Deterministic simulation engine for Tribes. Implements `createWorld: CreateWorld`4+from `@tribes/protocol`. Integer-only math, seeded PRNG (splitmix32), entities5+iterated by ascending id — the same config produces bit-identical runs in Node6+and browsers.7+8+## System order per `step()`9+10+1. **Orders** — validated and applied per tribe (ascending tribe id, orders in11+ array order). Invalid orders are silently dropped.12+2. **Army intent** — armies (ascending id) compute goals: attack marches to its13+ target; defend anchors on the tribe's town center; roam picks random passable14+ waypoints (the only PRNG use during a step).15+3. **Movement & work** — units (ascending id): army units fight-lock next to16+ enemies, attack-stance units divert to enemies within radius 3; gatherers17+ harvest an adjacent tile or take one greedy step toward the nearest matching18+ resource (Chebyshev distance, ties by lowest tile index, cached target);19+ builders add progress to the nearest own site or walk to it.20+4. **Combat** — units (ascending id) deal fixed damage sequentially to the21+ lowest-hp adjacent enemy unit (tie: lowest id); soldiers with no adjacent22+ enemy unit siege the lowest-hp adjacent enemy building. Then completed towers23+ (ascending id) shoot the nearest enemy unit within range 5.24+5. **Upkeep** — every 50 ticks each unit eats 1 food; a shortfall kills that many25+ lowest-id units (starvation gives no kill credit).26+6. **Death sweep** — units/buildings at hp ≤ 0 are removed; empty armies disband.27+7. **Elimination** — a tribe with no town center **and** no units is eliminated;28+ its remaining buildings vanish (no ruins). `forfeit()` does the same29+ immediately.30+8. `tick++`; the sim ends when ≤ 1 tribe is alive (`last_standing`) or31+ `maxTicks` is reached (`tick_cap`).32+33+## Rules34+35+- **Map** (`mapgen.ts`): grass base, water lakes (impassable), scattered36+ forest/berry/rock patches. Spawns sit evenly on a ring (integer 16-point37+ circle — no trig floats); each spawn is cleared and gets guaranteed38+ forest/berry/rock clusters nearby. Each tribe starts with a town center,39+ 3 villagers, 100 food / 50 wood / 0 stone.40+- **Gathering**: forest→wood, berry→food, rock→stone, 1/tick per villager while41+ adjacent-or-on the tile. Tiles hold 250 and turn to grass when exhausted.42+ Stockpiles cap at 300 per resource, +200 per completed granary.43+- **Movement**: 8-directional, 1 tile/tick, greedy step with deterministic44+ slide-around; water and completed walls block; units may stack on a tile.45+- **Buildings** (cost → hp): house 30w → 100 (+5 pop), granary 40w → 150,46+ barracks 50w → 200, tower 20w+30s → 250, wall 10s → 300. Town center47+ (hp 500, +10 pop) exists only at spawn and cannot be rebuilt. `build` orders48+ need a grass tile, pay up-front, and place a progress-0 site; each adjacent49+ `build`-job villager adds 5 progress/tick. Incomplete buildings have no50+ function but are attackable.51+- **Units**: villager (30f, hp 25, dmg 1) trains at a completed town center;52+ soldier (40f+20w, hp 60, dmg 5) at a completed barracks. Training is instant,53+ needs free pop cap, and spawns on the building's tile.54+- **Armies**: soldiers only. *attack* marches to the target and engages enemies55+ within radius 3 en route; *defend* holds within 4 of the town center and56+ intercepts enemies within 8 of it; *roam* patrols random waypoints and loots —57+ adjacent enemy buildings are damaged by generic soldier combat.58+- **Towers**: range 5 (Chebyshev), 6 dmg/tick, nearest enemy unit first.59+- **Fog of war**: everything reveals radius 6. `TribeView.terrain` shows `?`60+ for never-explored tiles; `enemies` lists only entities inside current vision.61+- **Score**: `pop*2 + buildings*5 + kills*3` (unit kills only, including tower62+ kills; starvation and eliminations credit nobody).63+- **Hash**: `stateHash()` = two FNV-1a-style 32-bit streams over a canonical64+ serialization (tick, PRNG state, terrain, amounts, tribes + explored masks,65+ units, buildings, armies), hex-concatenated to 64 bits.66+67+## Tests68+69+```sh70+cd packages/engine && npx vitest run71+```72+73+Golden-replay hashes in `test/engine.test.ts` pin exact state at ticks74+0/100/1000; regenerate them whenever simulation behavior intentionally changes.
A packages/engine/package.json +18 -0
A packages/engine/src/constants.ts +58 -0
@@ -0,0 +1,58 @@
1+import type { BuildingKind, ResourceKind, UnitKind } from '@tribes/protocol';2+3+/** Vision radius (Chebyshev) of every unit and building. */4+export const VISION_RADIUS = 6;5+/** Attack-stance armies divert toward enemies within this radius en route. */6+export const ENGAGE_RADIUS = 3;7+export const TOWER_RANGE = 5;8+export const TOWER_DAMAGE = 6;9+/** Harvestable amount on each resource tile; tile becomes grass at 0. */10+export const TILE_AMOUNT = 250;11+/** Per-resource stockpile cap without granaries. */12+export const BASE_STORAGE = 300;13+/** Additional per-resource cap per completed granary. */14+export const GRANARY_STORAGE = 200;15+export const START_RESOURCES: Record<ResourceKind, number> = {16+ food: 100,17+ wood: 50,18+ stone: 0,19+};20+/** Every UPKEEP_INTERVAL ticks each unit eats 1 food; shortfall kills lowest-id units. */21+export const UPKEEP_INTERVAL = 50;22+/** Construction progress a villager adds per tick when adjacent to a site. */23+export const BUILD_RATE = 5;24+/** Resource units harvested per villager per tick. */25+export const GATHER_RATE = 1;26+/** Defend-stance units hold within this radius of their town center. */27+export const DEFEND_HOLD_RADIUS = 4;28+/** Defend-stance units intercept enemies within this radius of the town center. */29+export const DEFEND_INTERCEPT_RADIUS = 8;30+/** Attempts to pick a passable random roam waypoint before giving up. */31+export const ROAM_PICK_TRIES = 8;32+33+export interface UnitStats {34+ hp: number;35+ damage: number;36+ cost: Partial<Record<ResourceKind, number>>;37+}38+39+export const UNIT_STATS: Record<UnitKind, UnitStats> = {40+ villager: { hp: 25, damage: 1, cost: { food: 30 } },41+ soldier: { hp: 60, damage: 5, cost: { food: 40, wood: 20 } },42+};43+44+export interface BuildingStats {45+ hp: number;46+ popCap: number;47+ /** null = cannot be built via orders (town_center exists only at spawn). */48+ cost: Partial<Record<ResourceKind, number>> | null;49+}50+51+export const BUILDING_STATS: Record<BuildingKind, BuildingStats> = {52+ town_center: { hp: 500, popCap: 10, cost: null },53+ house: { hp: 100, popCap: 5, cost: { wood: 30 } },54+ granary: { hp: 150, popCap: 0, cost: { wood: 40 } },55+ barracks: { hp: 200, popCap: 0, cost: { wood: 50 } },56+ tower: { hp: 250, popCap: 0, cost: { wood: 20, stone: 30 } },57+ wall: { hp: 300, popCap: 0, cost: { stone: 10 } },58+};
A packages/engine/src/hash.ts +37 -0
A packages/engine/src/index.ts +8 -0
A packages/engine/src/mapgen.ts +159 -0
@@ -0,0 +1,159 @@
1+/**2+ * Seed-deterministic map generation. Integer math only.3+ *4+ * Layout: grass base; a handful of water lakes (movement blockers); scattered5+ * forest/berry/rock patches; tribe spawns evenly spaced on a ring around the6+ * map center (integer 16-point circle table — no trig floats). Each spawn area7+ * is cleared to grass and gets guaranteed forest/berry/rock clusters at fixed8+ * offsets so every tribe has a playable start.9+ */10+import { TILE_AMOUNT } from './constants.js';11+import type { Rng } from './rng.js';12+13+export const T = {14+ GRASS: 0,15+ WATER: 1,16+ FOREST: 2,17+ BERRY: 3,18+ ROCK: 4,19+} as const;20+21+/** Terrain code -> TribeView char ('?' handled by fog). */22+export const T_CHAR = 'gwfbr';23+24+/** 16 points on a radius-1000 circle, integer-rounded (index k = k * 22.5deg). */25+const CIRCLE_16: ReadonlyArray<readonly [number, number]> = [26+ [1000, 0],27+ [924, 383],28+ [707, 707],29+ [383, 924],30+ [0, 1000],31+ [-383, 924],32+ [-707, 707],33+ [-924, 383],34+ [-1000, 0],35+ [-924, -383],36+ [-707, -707],37+ [-383, -924],38+ [0, -1000],39+ [383, -924],40+ [707, -707],41+ [924, -383],42+];43+44+/** Guaranteed resource clusters, as offsets from each spawn point. */45+const SPAWN_FOREST: ReadonlyArray<readonly [number, number]> = [46+ [3, -1],47+ [3, 0],48+ [3, 1],49+ [4, -1],50+ [4, 0],51+ [4, 1],52+];53+const SPAWN_BERRY: ReadonlyArray<readonly [number, number]> = [54+ [-3, -1],55+ [-3, 0],56+ [-3, 1],57+ [-4, 0],58+];59+const SPAWN_ROCK: ReadonlyArray<readonly [number, number]> = [60+ [-1, 3],61+ [0, 3],62+ [1, 3],63+ [0, 4],64+];65+66+export interface GeneratedMap {67+ terrain: Uint8Array;68+ /** Remaining harvestable amount per tile (0 for non-resource tiles). */69+ amount: Uint16Array;70+ spawns: Array<{ x: number; y: number }>;71+}72+73+function blob(74+ terrain: Uint8Array,75+ size: number,76+ cx: number,77+ cy: number,78+ r: number,79+ t: number,80+ overwrite: boolean,81+): void {82+ for (let dy = -r; dy <= r; dy++) {83+ for (let dx = -r; dx <= r; dx++) {84+ if (dx * dx + dy * dy > r * r) continue;85+ const x = cx + dx;86+ const y = cy + dy;87+ if (x < 0 || y < 0 || x >= size || y >= size) continue;88+ const i = y * size + x;89+ if (overwrite || terrain[i] === T.GRASS) terrain[i] = t;90+ }91+ }92+}93+94+export function generateMap(size: number, tribeCount: number, rng: Rng): GeneratedMap {95+ const terrain = new Uint8Array(size * size); // all grass96+ const margin = 6;97+ const span = size - margin * 2;98+99+ // Water lakes.100+ const lakes = 3 + rng.int(3);101+ for (let i = 0; i < lakes; i++) {102+ const cx = margin + rng.int(span);103+ const cy = margin + rng.int(span);104+ const r = 2 + rng.int(3);105+ blob(terrain, size, cx, cy, r, T.WATER, true);106+ }107+108+ // Scattered resource patches (only over grass).109+ const scatter = (t: number, count: number, rMax: number): void => {110+ for (let i = 0; i < count; i++) {111+ const cx = 2 + rng.int(size - 4);112+ const cy = 2 + rng.int(size - 4);113+ const r = 1 + rng.int(rMax);114+ blob(terrain, size, cx, cy, r, t, false);115+ }116+ };117+ scatter(T.FOREST, 12, 2);118+ scatter(T.BERRY, 8, 1);119+ scatter(T.ROCK, 8, 1);120+121+ // Spawns on a ring; clear the area and stamp guaranteed clusters.122+ const c = size >> 1;123+ const ringR = Math.floor((size * 3) / 8);124+ const spawns: Array<{ x: number; y: number }> = [];125+ for (let i = 0; i < tribeCount; i++) {126+ const p = CIRCLE_16[Math.floor((i * 16) / tribeCount) % 16]!;127+ const x = c + Math.trunc((p[0] * ringR) / 1000);128+ const y = c + Math.trunc((p[1] * ringR) / 1000);129+ spawns.push({ x, y });130+ // Clear a Chebyshev-radius-3 square to grass.131+ for (let dy = -3; dy <= 3; dy++) {132+ for (let dx = -3; dx <= 3; dx++) {133+ const px = x + dx;134+ const py = y + dy;135+ if (px < 0 || py < 0 || px >= size || py >= size) continue;136+ terrain[py * size + px] = T.GRASS;137+ }138+ }139+ const stamp = (offs: ReadonlyArray<readonly [number, number]>, t: number): void => {140+ for (const [dx, dy] of offs) {141+ const px = x + dx;142+ const py = y + dy;143+ if (px < 0 || py < 0 || px >= size || py >= size) continue;144+ terrain[py * size + px] = t;145+ }146+ };147+ stamp(SPAWN_FOREST, T.FOREST);148+ stamp(SPAWN_BERRY, T.BERRY);149+ stamp(SPAWN_ROCK, T.ROCK);150+ }151+152+ const amount = new Uint16Array(size * size);153+ for (let i = 0; i < terrain.length; i++) {154+ const t = terrain[i]!;155+ if (t === T.FOREST || t === T.BERRY || t === T.ROCK) amount[i] = TILE_AMOUNT;156+ }157+158+ return { terrain, amount, spawns };159+}
A packages/engine/src/rng.ts +43 -0
A packages/engine/src/world.ts +1171 -0
@@ -0,0 +1,1171 @@
1+/**2+ * Tribes simulation engine. Deterministic, integer-only.3+ *4+ * System order per step() (documented in packages/engine/README.md):5+ * 1. Order validation & application (tribes ascending id; orders in array order)6+ * 2. Army intent (armies ascending id; roam waypoints may consume PRNG)7+ * 3. Movement & work (units ascending id: fight-lock / gather / build / march)8+ * 4. Combat (units ascending id, sequential damage; then towers ascending id)9+ * 5. Upkeep & starvation (every UPKEEP_INTERVAL ticks)10+ * 6. Death sweep (units/buildings hp<=0 removed; empty armies disbanded)11+ * 7. Elimination check (no town_center AND no units -> tribe removed)12+ * 8. tick++, end-condition check13+ */14+import type {15+ ArmyStance,16+ ArmyView,17+ BuildingKind,18+ BuildingView,19+ CreateWorld,20+ EngineWorld,21+ EnemySighting,22+ Job,23+ Order,24+ ResourceKind,25+ Resources,26+ SimConfig,27+ SimResult,28+ TribeResult,29+ TribeView,30+ UnitKind,31+ UnitView,32+} from '@tribes/protocol';33+import { LIMITS } from '@tribes/protocol';34+import {35+ BASE_STORAGE,36+ BUILDING_STATS,37+ BUILD_RATE,38+ DEFEND_HOLD_RADIUS,39+ DEFEND_INTERCEPT_RADIUS,40+ ENGAGE_RADIUS,41+ GATHER_RATE,42+ GRANARY_STORAGE,43+ ROAM_PICK_TRIES,44+ START_RESOURCES,45+ TOWER_DAMAGE,46+ TOWER_RANGE,47+ UNIT_STATS,48+ UPKEEP_INTERVAL,49+ VISION_RADIUS,50+} from './constants.js';51+import { Hasher } from './hash.js';52+import { generateMap, T, T_CHAR } from './mapgen.js';53+import { Rng } from './rng.js';54+55+// ---------------------------------------------------------------------------56+// Internal state types57+// ---------------------------------------------------------------------------58+59+interface Unit {60+ id: number;61+ tribe: number;62+ kind: UnitKind;63+ x: number;64+ y: number;65+ hp: number;66+ job: Job;67+ /** -1 = not in an army. */68+ armyId: number;69+ /** Cached gather-target tile (-1 = none). */70+ tx: number;71+ ty: number;72+}73+74+interface Building {75+ id: number;76+ tribe: number;77+ kind: BuildingKind;78+ x: number;79+ y: number;80+ hp: number;81+ /** 0..100; 100 = complete. */82+ progress: number;83+}84+85+interface Army {86+ id: number;87+ tribe: number;88+ stance: ArmyStance;89+ /** Sorted ascending. */90+ unitIds: number[];91+ /** Waypoint (-1 = none). */92+ tx: number;93+ ty: number;94+}95+96+interface TribeState {97+ id: number;98+ alive: boolean;99+ /** -1 = still alive. */100+ diedAt: number;101+ res: Resources;102+ kills: number;103+ explored: Uint8Array;104+}105+106+const JOBS: ReadonlySet<string> = new Set([107+ 'idle',108+ 'gather_food',109+ 'gather_wood',110+ 'gather_stone',111+ 'build',112+]);113+const STANCES: ReadonlySet<string> = new Set(['attack', 'defend', 'roam']);114+const UNIT_KIND_CODE: Record<UnitKind, number> = { villager: 0, soldier: 1 };115+const BUILDING_KIND_CODE: Record<BuildingKind, number> = {116+ town_center: 0,117+ house: 1,118+ granary: 2,119+ barracks: 3,120+ tower: 4,121+ wall: 5,122+};123+const JOB_CODE: Record<Job, number> = {124+ idle: 0,125+ gather_food: 1,126+ gather_wood: 2,127+ gather_stone: 3,128+ build: 4,129+};130+const STANCE_CODE: Record<ArmyStance, number> = { attack: 0, defend: 1, roam: 2 };131+132+/** gather job -> terrain code it harvests. */133+const JOB_TERRAIN: Partial<Record<Job, number>> = {134+ gather_food: T.BERRY,135+ gather_wood: T.FOREST,136+ gather_stone: T.ROCK,137+};138+/** terrain code -> stockpile resource it yields. */139+const TERRAIN_RES: Record<number, ResourceKind> = {140+ [T.BERRY]: 'food',141+ [T.FOREST]: 'wood',142+ [T.ROCK]: 'stone',143+};144+145+const cheb = (ax: number, ay: number, bx: number, by: number): number =>146+ Math.max(Math.abs(ax - bx), Math.abs(ay - by));147+148+// ---------------------------------------------------------------------------149+// World150+// ---------------------------------------------------------------------------151+152+class World implements EngineWorld {153+ tick = 0;154+ readonly config: SimConfig;155+156+ private readonly rng: Rng;157+ private readonly size: number;158+ private readonly terrain: Uint8Array;159+ private readonly amount: Uint16Array;160+ private readonly tribes: TribeState[] = [];161+ /** All kept sorted ascending by id (ids are handed out monotonically). */162+ private readonly units: Unit[] = [];163+ private readonly buildings: Building[] = [];164+ private readonly armies: Army[] = [];165+ /** One building per tile: tile index -> building. */166+ private readonly buildingAt = new Map<number, Building>();167+ /** Tile index -> live resource-tile candidates, pruned lazily. */168+ private readonly resTiles: Record<number, number[]>;169+ /** Rebuilt each step: tile index -> units on it. */170+ private grid = new Map<number, Unit[]>();171+172+ private nextUnitId = 1;173+ private nextBuildingId = 1;174+ private nextArmyId = 1;175+ private over = false;176+ private endReason: 'last_standing' | 'tick_cap' = 'tick_cap';177+178+ constructor(config: SimConfig) {179+ if (180+ !Number.isInteger(config.tribeCount) ||181+ config.tribeCount < LIMITS.MIN_TRIBES ||182+ config.tribeCount > LIMITS.MAX_TRIBES183+ ) {184+ throw new Error(`tribeCount out of range: ${config.tribeCount}`);185+ }186+ if (!Number.isInteger(config.mapSize) || config.mapSize < 24 || config.mapSize > 256) {187+ throw new Error(`mapSize out of range: ${config.mapSize}`);188+ }189+ if (!Number.isInteger(config.maxTicks) || config.maxTicks < 1) {190+ throw new Error(`maxTicks out of range: ${config.maxTicks}`);191+ }192+ this.config = { ...config };193+ this.size = config.mapSize;194+ this.rng = new Rng(config.seed);195+196+ const map = generateMap(this.size, config.tribeCount, this.rng);197+ this.terrain = map.terrain;198+ this.amount = map.amount;199+ this.resTiles = { [T.FOREST]: [], [T.BERRY]: [], [T.ROCK]: [] };200+ for (let i = 0; i < this.terrain.length; i++) {201+ const t = this.terrain[i]!;202+ if (t === T.FOREST || t === T.BERRY || t === T.ROCK) this.resTiles[t]!.push(i);203+ }204+205+ for (let t = 0; t < config.tribeCount; t++) {206+ this.tribes.push({207+ id: t,208+ alive: true,209+ diedAt: -1,210+ res: { ...START_RESOURCES },211+ kills: 0,212+ explored: new Uint8Array(this.size * this.size),213+ });214+ }215+ // Spawn: town_center + 3 villagers per tribe (fixed creation order = ids).216+ const VILLAGER_OFFSETS: ReadonlyArray<readonly [number, number]> = [217+ [-1, 0],218+ [1, 0],219+ [0, 1],220+ ];221+ for (let t = 0; t < config.tribeCount; t++) {222+ const s = map.spawns[t]!;223+ this.addBuilding(t, 'town_center', s.x, s.y, 100);224+ for (const [dx, dy] of VILLAGER_OFFSETS) {225+ this.addUnit(t, 'villager', s.x + dx, s.y + dy);226+ }227+ }228+ }229+230+ // -- entity helpers -------------------------------------------------------231+232+ private addBuilding(233+ tribe: number,234+ kind: BuildingKind,235+ x: number,236+ y: number,237+ progress: number,238+ ): Building {239+ const b: Building = {240+ id: this.nextBuildingId++,241+ tribe,242+ kind,243+ x,244+ y,245+ hp: BUILDING_STATS[kind].hp,246+ progress,247+ };248+ this.buildings.push(b);249+ this.buildingAt.set(y * this.size + x, b);250+ this.reveal(tribe, x, y);251+ return b;252+ }253+254+ private addUnit(tribe: number, kind: UnitKind, x: number, y: number): Unit {255+ const u: Unit = {256+ id: this.nextUnitId++,257+ tribe,258+ kind,259+ x,260+ y,261+ hp: UNIT_STATS[kind].hp,262+ job: 'idle',263+ armyId: -1,264+ tx: -1,265+ ty: -1,266+ };267+ this.units.push(u);268+ this.reveal(tribe, x, y);269+ return u;270+ }271+272+ private findUnit(id: number): Unit | undefined {273+ // Binary search over the ascending-id array.274+ let lo = 0;275+ let hi = this.units.length - 1;276+ while (lo <= hi) {277+ const mid = (lo + hi) >> 1;278+ const u = this.units[mid]!;279+ if (u.id === id) return u;280+ if (u.id < id) lo = mid + 1;281+ else hi = mid - 1;282+ }283+ return undefined;284+ }285+286+ private findArmy(id: number): Army | undefined {287+ return this.armies.find((a) => a.id === id);288+ }289+290+ private reveal(tribe: number, x: number, y: number): void {291+ const ex = this.tribes[tribe]!.explored;292+ const s = this.size;293+ const x0 = Math.max(0, x - VISION_RADIUS);294+ const x1 = Math.min(s - 1, x + VISION_RADIUS);295+ const y0 = Math.max(0, y - VISION_RADIUS);296+ const y1 = Math.min(s - 1, y + VISION_RADIUS);297+ for (let yy = y0; yy <= y1; yy++) {298+ const row = yy * s;299+ for (let xx = x0; xx <= x1; xx++) ex[row + xx] = 1;300+ }301+ }302+303+ private passable(x: number, y: number): boolean {304+ if (x < 0 || y < 0 || x >= this.size || y >= this.size) return false;305+ const i = y * this.size + x;306+ if (this.terrain[i] === T.WATER) return false;307+ const b = this.buildingAt.get(i);308+ return !(b !== undefined && b.kind === 'wall' && b.progress >= 100);309+ }310+311+ private popOf(tribe: number): number {312+ let n = 0;313+ for (const u of this.units) if (u.tribe === tribe && u.hp > 0) n++;314+ return n;315+ }316+317+ private popCapOf(tribe: number): number {318+ let cap = 0;319+ for (const b of this.buildings) {320+ if (b.tribe !== tribe || b.progress < 100 || b.hp <= 0) continue;321+ cap += BUILDING_STATS[b.kind].popCap;322+ }323+ return cap;324+ }325+326+ private storageCapOf(tribe: number): number {327+ let cap = BASE_STORAGE;328+ for (const b of this.buildings) {329+ if (b.tribe === tribe && b.kind === 'granary' && b.progress >= 100 && b.hp > 0) {330+ cap += GRANARY_STORAGE;331+ }332+ }333+ return cap;334+ }335+336+ private canAfford(res: Resources, cost: Partial<Record<ResourceKind, number>>): boolean {337+ return (338+ res.food >= (cost.food ?? 0) &&339+ res.wood >= (cost.wood ?? 0) &&340+ res.stone >= (cost.stone ?? 0)341+ );342+ }343+344+ private pay(res: Resources, cost: Partial<Record<ResourceKind, number>>): void {345+ res.food -= cost.food ?? 0;346+ res.wood -= cost.wood ?? 0;347+ res.stone -= cost.stone ?? 0;348+ }349+350+ // -- step -----------------------------------------------------------------351+352+ step(ordersByTribe: ReadonlyMap<number, Order[]>): void {353+ if (this.over) return;354+355+ // 1. Orders.356+ for (const tribe of this.tribes) {357+ if (!tribe.alive) continue;358+ const orders = ordersByTribe.get(tribe.id);359+ if (!Array.isArray(orders)) continue;360+ for (const order of orders) this.applyOrder(tribe, order);361+ }362+363+ // 2. Army intent.364+ this.rebuildGrid();365+ const goals = this.decideArmyIntent();366+367+ // 3. Movement & work.368+ for (const u of this.units) {369+ if (u.hp <= 0) continue;370+ this.actUnit(u, goals);371+ }372+373+ // 4. Combat.374+ this.rebuildGrid();375+ this.combat();376+377+ // 5. Upkeep.378+ if ((this.tick + 1) % UPKEEP_INTERVAL === 0) this.upkeep();379+380+ // 6. Death sweep.381+ this.sweepDead();382+383+ // 7. Eliminations.384+ for (const tribe of this.tribes) {385+ if (!tribe.alive) continue;386+ let hasTc = false;387+ for (const b of this.buildings) {388+ if (b.tribe === tribe.id && b.kind === 'town_center') {389+ hasTc = true;390+ break;391+ }392+ }393+ let hasUnit = false;394+ for (const u of this.units) {395+ if (u.tribe === tribe.id) {396+ hasUnit = true;397+ break;398+ }399+ }400+ if (!hasTc && !hasUnit) this.eliminate(tribe.id);401+ }402+403+ // 8. Advance & end check.404+ this.tick++;405+ this.checkEnd();406+ }407+408+ private checkEnd(): void {409+ const alive = this.tribes.filter((t) => t.alive).length;410+ if (alive <= 1) {411+ this.over = true;412+ this.endReason = 'last_standing';413+ } else if (this.tick >= this.config.maxTicks) {414+ this.over = true;415+ this.endReason = 'tick_cap';416+ }417+ }418+419+ /** Forfeit: the tribe's units and buildings vanish immediately. */420+ forfeit(tribeId: number): void {421+ const tribe = this.tribes[tribeId];422+ if (tribe === undefined || !tribe.alive || this.over) return;423+ this.eliminate(tribeId);424+ this.checkEnd();425+ }426+427+ private eliminate(tribeId: number): void {428+ const tribe = this.tribes[tribeId]!;429+ tribe.alive = false;430+ tribe.diedAt = this.tick;431+ for (let i = this.units.length - 1; i >= 0; i--) {432+ if (this.units[i]!.tribe === tribeId) this.units.splice(i, 1);433+ }434+ for (let i = this.buildings.length - 1; i >= 0; i--) {435+ const b = this.buildings[i]!;436+ if (b.tribe === tribeId) {437+ this.buildingAt.delete(b.y * this.size + b.x);438+ this.buildings.splice(i, 1);439+ }440+ }441+ for (let i = this.armies.length - 1; i >= 0; i--) {442+ if (this.armies[i]!.tribe === tribeId) this.armies.splice(i, 1);443+ }444+ }445+446+ // -- orders ---------------------------------------------------------------447+448+ private applyOrder(tribe: TribeState, order: Order): void {449+ if (order === null || typeof order !== 'object') return;450+ switch (order.kind) {451+ case 'assign': {452+ if (!Number.isInteger(order.unitId) || !JOBS.has(order.job as string)) return;453+ const u = this.findUnit(order.unitId);454+ if (u === undefined || u.tribe !== tribe.id || u.kind !== 'villager') return;455+ if (u.armyId !== -1) return;456+ u.job = order.job;457+ u.tx = -1;458+ u.ty = -1;459+ return;460+ }461+ case 'build': {462+ const stats = BUILDING_STATS[order.building as BuildingKind] as463+ | (typeof BUILDING_STATS)[BuildingKind]464+ | undefined;465+ if (stats === undefined || stats.cost === null) return;466+ const { x, y } = order;467+ if (!Number.isInteger(x) || !Number.isInteger(y)) return;468+ if (x < 0 || y < 0 || x >= this.size || y >= this.size) return;469+ const i = y * this.size + x;470+ if (this.terrain[i] !== T.GRASS) return;471+ if (this.buildingAt.has(i)) return;472+ if (!this.canAfford(tribe.res, stats.cost)) return;473+ this.pay(tribe.res, stats.cost);474+ this.addBuilding(tribe.id, order.building, x, y, 0);475+ return;476+ }477+ case 'train': {478+ const stats = UNIT_STATS[order.unit as UnitKind] as479+ | (typeof UNIT_STATS)[UnitKind]480+ | undefined;481+ if (stats === undefined) return;482+ const siteKind: BuildingKind = order.unit === 'soldier' ? 'barracks' : 'town_center';483+ let site: Building | undefined;484+ for (const b of this.buildings) {485+ if (b.tribe === tribe.id && b.kind === siteKind && b.progress >= 100 && b.hp > 0) {486+ site = b;487+ break;488+ }489+ }490+ if (site === undefined) return;491+ if (this.popOf(tribe.id) >= this.popCapOf(tribe.id)) return;492+ if (!this.canAfford(tribe.res, stats.cost)) return;493+ this.pay(tribe.res, stats.cost);494+ this.addUnit(tribe.id, order.unit, site.x, site.y);495+ return;496+ }497+ case 'form_army': {498+ if (!Array.isArray(order.unitIds) || !STANCES.has(order.stance as string)) return;499+ const members: Unit[] = [];500+ const seen = new Set<number>();501+ for (const id of order.unitIds) {502+ if (!Number.isInteger(id) || seen.has(id)) continue;503+ seen.add(id);504+ const u = this.findUnit(id);505+ if (506+ u !== undefined &&507+ u.tribe === tribe.id &&508+ u.kind === 'soldier' &&509+ u.armyId === -1 &&510+ u.hp > 0511+ ) {512+ members.push(u);513+ }514+ }515+ if (members.length === 0) return;516+ members.sort((a, b) => a.id - b.id);517+ const army: Army = {518+ id: this.nextArmyId++,519+ tribe: tribe.id,520+ stance: order.stance,521+ unitIds: members.map((m) => m.id),522+ tx: -1,523+ ty: -1,524+ };525+ for (const m of members) m.armyId = army.id;526+ this.armies.push(army);527+ return;528+ }529+ case 'set_stance': {530+ if (!Number.isInteger(order.armyId) || !STANCES.has(order.stance as string)) return;531+ const army = this.findArmy(order.armyId);532+ if (army === undefined || army.tribe !== tribe.id) return;533+ army.stance = order.stance;534+ return;535+ }536+ case 'move_army': {537+ if (!Number.isInteger(order.armyId)) return;538+ const { x, y } = order;539+ if (!Number.isInteger(x) || !Number.isInteger(y)) return;540+ if (x < 0 || y < 0 || x >= this.size || y >= this.size) return;541+ const army = this.findArmy(order.armyId);542+ if (army === undefined || army.tribe !== tribe.id) return;543+ army.tx = x;544+ army.ty = y;545+ return;546+ }547+ case 'disband_army': {548+ if (!Number.isInteger(order.armyId)) return;549+ const idx = this.armies.findIndex((a) => a.id === order.armyId);550+ if (idx === -1 || this.armies[idx]!.tribe !== tribe.id) return;551+ for (const id of this.armies[idx]!.unitIds) {552+ const u = this.findUnit(id);553+ if (u !== undefined) u.armyId = -1;554+ }555+ this.armies.splice(idx, 1);556+ return;557+ }558+ default:559+ return;560+ }561+ }562+563+ // -- army intent ----------------------------------------------------------564+565+ /** Per-army marching goal; defend armies also get an intruder list. */566+ private decideArmyIntent(): Map<number, { x: number; y: number; intruders: Unit[] | null }> {567+ const goals = new Map<number, { x: number; y: number; intruders: Unit[] | null }>();568+ for (const army of this.armies) {569+ if (army.stance === 'defend') {570+ let tc: Building | undefined;571+ for (const b of this.buildings) {572+ if (b.tribe === army.tribe && b.kind === 'town_center') {573+ tc = b;574+ break;575+ }576+ }577+ if (tc === undefined) continue; // hold position578+ const intruders: Unit[] = [];579+ for (const u of this.units) {580+ if (u.tribe === army.tribe || u.hp <= 0) continue;581+ if (cheb(u.x, u.y, tc.x, tc.y) <= DEFEND_INTERCEPT_RADIUS) intruders.push(u);582+ }583+ goals.set(army.id, { x: tc.x, y: tc.y, intruders });584+ continue;585+ }586+ if (army.stance === 'roam') {587+ // New waypoint when none, or when the reference (lowest-id) unit arrives.588+ const ref = this.findUnit(army.unitIds[0]!);589+ const arrived =590+ ref !== undefined && army.tx !== -1 && cheb(ref.x, ref.y, army.tx, army.ty) <= 1;591+ if (army.tx === -1 || arrived) {592+ for (let i = 0; i < ROAM_PICK_TRIES; i++) {593+ const x = this.rng.int(this.size);594+ const y = this.rng.int(this.size);595+ if (this.passable(x, y)) {596+ army.tx = x;597+ army.ty = y;598+ break;599+ }600+ }601+ }602+ }603+ if (army.tx !== -1) goals.set(army.id, { x: army.tx, y: army.ty, intruders: null });604+ }605+ return goals;606+ }607+608+ // -- movement & work ------------------------------------------------------609+610+ private unitsAt(x: number, y: number): Unit[] | undefined {611+ return this.grid.get(y * this.size + x);612+ }613+614+ private rebuildGrid(): void {615+ this.grid = new Map();616+ for (const u of this.units) {617+ if (u.hp <= 0) continue;618+ const i = u.y * this.size + u.x;619+ const list = this.grid.get(i);620+ if (list === undefined) this.grid.set(i, [u]);621+ else list.push(u);622+ }623+ }624+625+ /** Nearest live enemy unit within Chebyshev radius r (nearest, then lowest id). */626+ private nearestEnemyNear(u: Unit, r: number): Unit | null {627+ let best: Unit | null = null;628+ let bestD = r + 1;629+ const x0 = Math.max(0, u.x - r);630+ const x1 = Math.min(this.size - 1, u.x + r);631+ const y0 = Math.max(0, u.y - r);632+ const y1 = Math.min(this.size - 1, u.y + r);633+ for (let yy = y0; yy <= y1; yy++) {634+ for (let xx = x0; xx <= x1; xx++) {635+ const list = this.unitsAt(xx, yy);636+ if (list === undefined) continue;637+ for (const e of list) {638+ if (e.tribe === u.tribe || e.hp <= 0) continue;639+ const d = cheb(u.x, u.y, e.x, e.y);640+ if (d < bestD || (d === bestD && (best === null || e.id < best.id))) {641+ best = e;642+ bestD = d;643+ }644+ }645+ }646+ }647+ return best;648+ }649+650+ /** True if an enemy unit — or (for soldiers) enemy building — is in contact range. */651+ private inContactWithEnemy(u: Unit): boolean {652+ for (let dy = -1; dy <= 1; dy++) {653+ for (let dx = -1; dx <= 1; dx++) {654+ const x = u.x + dx;655+ const y = u.y + dy;656+ if (x < 0 || y < 0 || x >= this.size || y >= this.size) continue;657+ const list = this.unitsAt(x, y);658+ if (list !== undefined) {659+ for (const e of list) if (e.tribe !== u.tribe && e.hp > 0) return true;660+ }661+ if (u.kind === 'soldier') {662+ const b = this.buildingAt.get(y * this.size + x);663+ if (b !== undefined && b.tribe !== u.tribe && b.hp > 0) return true;664+ }665+ }666+ }667+ return false;668+ }669+670+ /** One greedy step toward (tx,ty), sliding around blockers. Deterministic. */671+ private moveTowards(u: Unit, tx: number, ty: number): void {672+ const dx = Math.sign(tx - u.x);673+ const dy = Math.sign(ty - u.y);674+ if (dx === 0 && dy === 0) return;675+ let nx = 0;676+ let ny = 0;677+ const tryStep = (sx: number, sy: number): boolean => {678+ if (sx === 0 && sy === 0) return false;679+ const px = u.x + sx;680+ const py = u.y + sy;681+ if (!this.passable(px, py)) return false;682+ nx = px;683+ ny = py;684+ return true;685+ };686+ const moved =687+ tryStep(dx, dy) ||688+ tryStep(dx, 0) ||689+ tryStep(0, dy) ||690+ (dx !== 0 && dy !== 0 && (tryStep(dx, -dy) || tryStep(-dx, dy))) ||691+ (dx === 0 && (tryStep(1, dy) || tryStep(-1, dy))) ||692+ (dy === 0 && (tryStep(dx, 1) || tryStep(dx, -1)));693+ if (moved) {694+ u.x = nx;695+ u.y = ny;696+ this.reveal(u.tribe, nx, ny);697+ }698+ }699+700+ private actUnit(701+ u: Unit,702+ goals: Map<number, { x: number; y: number; intruders: Unit[] | null }>,703+ ): void {704+ if (u.armyId !== -1) {705+ this.actArmyUnit(u, goals);706+ return;707+ }708+ if (u.kind !== 'villager') return; // idle soldiers hold (they still fight in combat)709+ if (u.job === 'build') {710+ this.actBuilder(u);711+ return;712+ }713+ const wanted = JOB_TERRAIN[u.job];714+ if (wanted !== undefined) this.actGatherer(u, wanted);715+ }716+717+ private actArmyUnit(718+ u: Unit,719+ goals: Map<number, { x: number; y: number; intruders: Unit[] | null }>,720+ ): void {721+ // Fight-lock: never walk away from an adjacent enemy.722+ if (this.inContactWithEnemy(u)) return;723+ const army = this.findArmy(u.armyId);724+ if (army === undefined) return;725+ const goal = goals.get(u.armyId);726+ if (army.stance === 'attack') {727+ const e = this.nearestEnemyNear(u, ENGAGE_RADIUS);728+ if (e !== null) {729+ this.moveTowards(u, e.x, e.y);730+ return;731+ }732+ if (goal !== undefined) this.moveTowards(u, goal.x, goal.y);733+ return;734+ }735+ if (army.stance === 'defend') {736+ if (goal === undefined) return; // no town center: hold737+ const intruders = goal.intruders!;738+ if (intruders.length > 0) {739+ let best: Unit = intruders[0]!;740+ let bestD = cheb(u.x, u.y, best.x, best.y);741+ for (const e of intruders) {742+ const d = cheb(u.x, u.y, e.x, e.y);743+ if (d < bestD || (d === bestD && e.id < best.id)) {744+ best = e;745+ bestD = d;746+ }747+ }748+ this.moveTowards(u, best.x, best.y);749+ return;750+ }751+ if (cheb(u.x, u.y, goal.x, goal.y) > DEFEND_HOLD_RADIUS) {752+ this.moveTowards(u, goal.x, goal.y);753+ }754+ return;755+ }756+ // roam757+ if (goal !== undefined) this.moveTowards(u, goal.x, goal.y);758+ }759+760+ private actGatherer(u: Unit, wanted: number): void {761+ // Harvest if a matching tile is adjacent-or-underfoot (lowest tile index).762+ let harvestTile = -1;763+ for (let dy = -1; dy <= 1; dy++) {764+ for (let dx = -1; dx <= 1; dx++) {765+ const x = u.x + dx;766+ const y = u.y + dy;767+ if (x < 0 || y < 0 || x >= this.size || y >= this.size) continue;768+ const i = y * this.size + x;769+ if (this.terrain[i] !== wanted || this.amount[i] === 0) continue;770+ if (harvestTile === -1 || i < harvestTile) harvestTile = i;771+ }772+ }773+ if (harvestTile !== -1) {774+ const tribe = this.tribes[u.tribe]!;775+ const res = TERRAIN_RES[wanted]!;776+ if (tribe.res[res] < this.storageCapOf(u.tribe)) {777+ tribe.res[res] += GATHER_RATE;778+ const left = this.amount[harvestTile]! - GATHER_RATE;779+ this.amount[harvestTile] = Math.max(0, left);780+ if (left <= 0) this.terrain[harvestTile] = T.GRASS; // pruned from resTiles lazily781+ }782+ return;783+ }784+ // Walk toward the cached target, re-searching when it is gone.785+ const cachedIdx = u.tx === -1 ? -1 : u.ty * this.size + u.tx;786+ const cachedValid =787+ cachedIdx !== -1 && this.terrain[cachedIdx] === wanted && this.amount[cachedIdx]! > 0;788+ if (!cachedValid) {789+ const found = this.findNearestResource(wanted, u.x, u.y);790+ if (found === -1) return; // nothing left of this kind791+ u.tx = found % this.size;792+ u.ty = Math.floor(found / this.size);793+ }794+ this.moveTowards(u, u.tx, u.ty);795+ }796+797+ /** Nearest live tile of terrain kind (Chebyshev; tie -> lowest tile index). Prunes dead entries. */798+ private findNearestResource(wanted: number, x: number, y: number): number {799+ const list = this.resTiles[wanted]!;800+ let best = -1;801+ let bestD = 0x7fffffff;802+ let w = 0;803+ for (let r = 0; r < list.length; r++) {804+ const i = list[r]!;805+ if (this.terrain[i] !== wanted || this.amount[i] === 0) continue; // prune806+ list[w++] = i;807+ const d = cheb(x, y, i % this.size, Math.floor(i / this.size));808+ if (d < bestD || (d === bestD && i < best)) {809+ best = i;810+ bestD = d;811+ }812+ }813+ list.length = w;814+ return best;815+ }816+817+ private actBuilder(u: Unit): void {818+ // Nearest own construction site (Chebyshev; tie -> lowest building id).819+ let site: Building | null = null;820+ let bestD = 0x7fffffff;821+ for (const b of this.buildings) {822+ if (b.tribe !== u.tribe || b.progress >= 100 || b.hp <= 0) continue;823+ const d = cheb(u.x, u.y, b.x, b.y);824+ if (d < bestD) {825+ site = b;826+ bestD = d;827+ }828+ }829+ if (site === null) return;830+ if (bestD <= 1) {831+ site.progress = Math.min(100, site.progress + BUILD_RATE);832+ } else {833+ this.moveTowards(u, site.x, site.y);834+ }835+ }836+837+ // -- combat ---------------------------------------------------------------838+839+ private combat(): void {840+ // Units, ascending id, sequential damage (a unit killed before its turn841+ // does not strike back this tick).842+ for (const u of this.units) {843+ if (u.hp <= 0) continue;844+ const dmg = UNIT_STATS[u.kind].damage;845+ // Lowest-hp adjacent enemy unit (tie -> lowest id).846+ let target: Unit | null = null;847+ for (let dy = -1; dy <= 1; dy++) {848+ for (let dx = -1; dx <= 1; dx++) {849+ const x = u.x + dx;850+ const y = u.y + dy;851+ if (x < 0 || y < 0 || x >= this.size || y >= this.size) continue;852+ const list = this.unitsAt(x, y);853+ if (list === undefined) continue;854+ for (const e of list) {855+ if (e.tribe === u.tribe || e.hp <= 0) continue;856+ if (857+ target === null ||858+ e.hp < target.hp ||859+ (e.hp === target.hp && e.id < target.id)860+ ) {861+ target = e;862+ }863+ }864+ }865+ }866+ if (target !== null) {867+ target.hp -= dmg;868+ if (target.hp <= 0) this.tribes[u.tribe]!.kills++;869+ continue;870+ }871+ // Soldiers siege adjacent enemy buildings (lowest hp, tie -> lowest id).872+ if (u.kind !== 'soldier') continue;873+ let bTarget: Building | null = null;874+ for (let dy = -1; dy <= 1; dy++) {875+ for (let dx = -1; dx <= 1; dx++) {876+ const x = u.x + dx;877+ const y = u.y + dy;878+ if (x < 0 || y < 0 || x >= this.size || y >= this.size) continue;879+ const b = this.buildingAt.get(y * this.size + x);880+ if (b === undefined || b.tribe === u.tribe || b.hp <= 0) continue;881+ if (882+ bTarget === null ||883+ b.hp < bTarget.hp ||884+ (b.hp === bTarget.hp && b.id < bTarget.id)885+ ) {886+ bTarget = b;887+ }888+ }889+ }890+ if (bTarget !== null) bTarget.hp -= dmg;891+ }892+893+ // Towers, ascending building id: shoot nearest enemy unit in range894+ // (nearest, then lowest id). Building kills also count as kills.895+ for (const b of this.buildings) {896+ if (b.kind !== 'tower' || b.progress < 100 || b.hp <= 0) continue;897+ let target: Unit | null = null;898+ let bestD = TOWER_RANGE + 1;899+ const x0 = Math.max(0, b.x - TOWER_RANGE);900+ const x1 = Math.min(this.size - 1, b.x + TOWER_RANGE);901+ const y0 = Math.max(0, b.y - TOWER_RANGE);902+ const y1 = Math.min(this.size - 1, b.y + TOWER_RANGE);903+ for (let yy = y0; yy <= y1; yy++) {904+ for (let xx = x0; xx <= x1; xx++) {905+ const list = this.unitsAt(xx, yy);906+ if (list === undefined) continue;907+ for (const e of list) {908+ if (e.tribe === b.tribe || e.hp <= 0) continue;909+ const d = cheb(b.x, b.y, e.x, e.y);910+ if (d < bestD || (d === bestD && (target === null || e.id < target.id))) {911+ target = e;912+ bestD = d;913+ }914+ }915+ }916+ }917+ if (target !== null) {918+ target.hp -= TOWER_DAMAGE;919+ if (target.hp <= 0) this.tribes[b.tribe]!.kills++;920+ }921+ }922+ }923+924+ private upkeep(): void {925+ for (const tribe of this.tribes) {926+ if (!tribe.alive) continue;927+ let need = 0;928+ for (const u of this.units) if (u.tribe === tribe.id && u.hp > 0) need++;929+ if (tribe.res.food >= need) {930+ tribe.res.food -= need;931+ continue;932+ }933+ let shortfall = need - tribe.res.food;934+ tribe.res.food = 0;935+ // Starvation kills lowest-id units first (no kill credit).936+ for (const u of this.units) {937+ if (shortfall === 0) break;938+ if (u.tribe === tribe.id && u.hp > 0) {939+ u.hp = 0;940+ shortfall--;941+ }942+ }943+ }944+ }945+946+ private sweepDead(): void {947+ let removedUnits = false;948+ for (let i = this.units.length - 1; i >= 0; i--) {949+ const u = this.units[i]!;950+ if (u.hp > 0) continue;951+ if (u.armyId !== -1) {952+ const army = this.findArmy(u.armyId);953+ if (army !== undefined) {954+ const k = army.unitIds.indexOf(u.id);955+ if (k !== -1) army.unitIds.splice(k, 1);956+ }957+ }958+ this.units.splice(i, 1);959+ removedUnits = true;960+ }961+ if (removedUnits) {962+ for (let i = this.armies.length - 1; i >= 0; i--) {963+ if (this.armies[i]!.unitIds.length === 0) this.armies.splice(i, 1);964+ }965+ }966+ for (let i = this.buildings.length - 1; i >= 0; i--) {967+ const b = this.buildings[i]!;968+ if (b.hp <= 0) {969+ this.buildingAt.delete(b.y * this.size + b.x);970+ this.buildings.splice(i, 1);971+ }972+ }973+ }974+975+ // -- views ----------------------------------------------------------------976+977+ viewFor(tribeId: number): TribeView {978+ const tribe = this.tribes[tribeId];979+ if (tribe === undefined) throw new RangeError(`no such tribe: ${tribeId}`);980+ const s = this.size;981+982+ // Current-vision mask (distinct from the persistent explored mask).983+ const visible = new Uint8Array(s * s);984+ const mark = (x: number, y: number): void => {985+ const x0 = Math.max(0, x - VISION_RADIUS);986+ const x1 = Math.min(s - 1, x + VISION_RADIUS);987+ const y0 = Math.max(0, y - VISION_RADIUS);988+ const y1 = Math.min(s - 1, y + VISION_RADIUS);989+ for (let yy = y0; yy <= y1; yy++) {990+ const row = yy * s;991+ for (let xx = x0; xx <= x1; xx++) visible[row + xx] = 1;992+ }993+ };994+ const units: UnitView[] = [];995+ for (const u of this.units) {996+ if (u.tribe !== tribeId) continue;997+ mark(u.x, u.y);998+ units.push({999+ id: u.id,1000+ kind: u.kind,1001+ x: u.x,1002+ y: u.y,1003+ hp: u.hp,1004+ job: u.job,1005+ armyId: u.armyId === -1 ? null : u.armyId,1006+ });1007+ }1008+ const buildings: BuildingView[] = [];1009+ for (const b of this.buildings) {1010+ if (b.tribe !== tribeId) continue;1011+ mark(b.x, b.y);1012+ buildings.push({ id: b.id, kind: b.kind, x: b.x, y: b.y, hp: b.hp, progress: b.progress });1013+ }1014+ const armies: ArmyView[] = [];1015+ for (const a of this.armies) {1016+ if (a.tribe !== tribeId) continue;1017+ armies.push({1018+ id: a.id,1019+ stance: a.stance,1020+ unitIds: [...a.unitIds],1021+ target: a.tx === -1 ? null : { x: a.tx, y: a.ty },1022+ });1023+ }1024+1025+ const chars = new Array<string>(s * s);1026+ const explored = tribe.explored;1027+ for (let i = 0; i < s * s; i++) {1028+ chars[i] = explored[i] === 1 ? T_CHAR[this.terrain[i]!]! : '?';1029+ }1030+1031+ const enemies: EnemySighting[] = [];1032+ for (const other of this.tribes) {1033+ if (other.id === tribeId || !other.alive) continue;1034+ const eUnits: EnemySighting['units'] = [];1035+ for (const u of this.units) {1036+ if (u.tribe !== other.id) continue;1037+ if (visible[u.y * s + u.x] === 1) {1038+ eUnits.push({ id: u.id, kind: u.kind, x: u.x, y: u.y, hp: u.hp });1039+ }1040+ }1041+ const eBuildings: EnemySighting['buildings'] = [];1042+ for (const b of this.buildings) {1043+ if (b.tribe !== other.id) continue;1044+ if (visible[b.y * s + b.x] === 1) {1045+ eBuildings.push({ id: b.id, kind: b.kind, x: b.x, y: b.y, hp: b.hp });1046+ }1047+ }1048+ if (eUnits.length > 0 || eBuildings.length > 0) {1049+ enemies.push({ tribeId: other.id, units: eUnits, buildings: eBuildings });1050+ }1051+ }1052+1053+ return {1054+ tick: this.tick,1055+ tribeId,1056+ mapSize: s,1057+ resources: { ...tribe.res },1058+ pop: units.length,1059+ popCap: this.popCapOf(tribeId),1060+ units,1061+ buildings,1062+ armies,1063+ terrain: chars.join(''),1064+ enemies,1065+ aliveTribes: this.tribes.filter((t) => t.alive).map((t) => t.id),1066+ };1067+ }1068+1069+ // -- results & hash -------------------------------------------------------1070+1071+ isOver(): boolean {1072+ return this.over;1073+ }1074+1075+ private scoreOf(tribe: TribeState): number {1076+ let numBuildings = 0;1077+ for (const b of this.buildings) if (b.tribe === tribe.id) numBuildings++;1078+ return this.popOf(tribe.id) * 2 + numBuildings * 5 + tribe.kills * 3;1079+ }1080+1081+ result(): SimResult {1082+ const tribes: TribeResult[] = this.tribes.map((t) => {1083+ let numBuildings = 0;1084+ for (const b of this.buildings) if (b.tribe === t.id) numBuildings++;1085+ return {1086+ tribeId: t.id,1087+ alive: t.alive,1088+ score: this.scoreOf(t),1089+ pop: this.popOf(t.id),1090+ kills: t.kills,1091+ buildings: numBuildings,1092+ diedAt: t.diedAt === -1 ? null : t.diedAt,1093+ };1094+ });1095+ const alive = tribes.filter((t) => t.alive);1096+ let winner: number | null = null;1097+ if (this.endReason === 'last_standing') {1098+ winner = alive.length === 1 ? alive[0]!.tribeId : null;1099+ } else {1100+ let best = -1;1101+ let tie = false;1102+ for (const t of alive) {1103+ if (t.score > best) {1104+ best = t.score;1105+ winner = t.tribeId;1106+ tie = false;1107+ } else if (t.score === best) {1108+ tie = true;1109+ }1110+ }1111+ if (tie || alive.length === 0) winner = null;1112+ }1113+ return { endedAt: this.tick, reason: this.endReason, winner, tribes };1114+ }1115+1116+ stateHash(): string {1117+ const h = new Hasher();1118+ h.int(this.tick);1119+ h.int(this.rng.state);1120+ h.int(this.over ? 1 : 0);1121+ h.int(this.nextUnitId);1122+ h.int(this.nextBuildingId);1123+ h.int(this.nextArmyId);1124+ h.bytes(this.terrain);1125+ for (let i = 0; i < this.amount.length; i++) {1126+ const a = this.amount[i]!;1127+ h.byte(a);1128+ h.byte(a >>> 8);1129+ }1130+ for (const t of this.tribes) {1131+ h.int(t.alive ? 1 : 0);1132+ h.int(t.diedAt);1133+ h.int(t.res.food);1134+ h.int(t.res.wood);1135+ h.int(t.res.stone);1136+ h.int(t.kills);1137+ h.bytes(t.explored);1138+ }1139+ for (const u of this.units) {1140+ h.int(u.id);1141+ h.int(u.tribe);1142+ h.int(UNIT_KIND_CODE[u.kind]);1143+ h.int(u.x);1144+ h.int(u.y);1145+ h.int(u.hp);1146+ h.int(JOB_CODE[u.job]);1147+ h.int(u.armyId);1148+ }1149+ for (const b of this.buildings) {1150+ h.int(b.id);1151+ h.int(b.tribe);1152+ h.int(BUILDING_KIND_CODE[b.kind]);1153+ h.int(b.x);1154+ h.int(b.y);1155+ h.int(b.hp);1156+ h.int(b.progress);1157+ }1158+ for (const a of this.armies) {1159+ h.int(a.id);1160+ h.int(a.tribe);1161+ h.int(STANCE_CODE[a.stance]);1162+ h.int(a.tx);1163+ h.int(a.ty);1164+ h.int(a.unitIds.length);1165+ for (const id of a.unitIds) h.int(id);1166+ }1167+ return h.digest();1168+ }1169+}1170+1171+export const createWorld: CreateWorld = (config: SimConfig) => new World(config);
A packages/engine/test/engine.test.ts +352 -0

Not shown: this page already shows 6000 lines of diff.

A packages/engine/tsconfig.json +9 -0

Not shown: this page already shows 6000 lines of diff.

A packages/engine/tsconfig.tsbuildinfo +1 -0

Not shown: this page already shows 6000 lines of diff.

A packages/protocol/package.json +18 -0

Not shown: this page already shows 6000 lines of diff.

A packages/protocol/src/api.ts +176 -0

Not shown: this page already shows 6000 lines of diff.

A packages/protocol/src/game.ts +224 -0

Not shown: this page already shows 6000 lines of diff.

A packages/protocol/src/index.ts +2 -0

Not shown: this page already shows 6000 lines of diff.

A packages/protocol/tsconfig.json +8 -0

Not shown: this page already shows 6000 lines of diff.

A packages/protocol/tsconfig.tsbuildinfo +1 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/bots/gatherer.js +104 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/bots/primitives.js +150 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/bots/rusher.js +127 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/bots/turtle.js +140 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/package.json +19 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/scripts/gen-bots.mjs +28 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/src/bots.ts +533 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/src/index.ts +489 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/src/prelude.ts +164 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/test/host.test.ts +404 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/test/primitives.test.ts +95 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/test/regex-gas.test.ts +51 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/tsconfig.json +9 -0

Not shown: this page already shows 6000 lines of diff.

A packages/script-host/tsconfig.tsbuildinfo +1 -0

Not shown: this page already shows 6000 lines of diff.

A scripts/import-assets.ts +160 -0

Not shown: this page already shows 6000 lines of diff.

A scripts/release.sh +62 -0

Not shown: this page already shows 6000 lines of diff.

A tsconfig.base.json +16 -0

Not shown: this page already shows 6000 lines of diff.

A web/index.html +14 -0

Not shown: this page already shows 6000 lines of diff.

A web/package.json +19 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/api.ts +83 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/auth.ts +64 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/dev-mock.ts +385 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/dom.ts +35 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/log.ts +36 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/main.ts +108 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/pages/home.ts +633 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/pages/viewer.ts +317 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/render/manifest.ts +41 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/render/renderer.ts +237 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/sim-worker.ts +300 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/sim/frames.ts +68 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/sim/runner.ts +127 -0

Not shown: this page already shows 6000 lines of diff.

A web/src/style.css +195 -0

Not shown: this page already shows 6000 lines of diff.

A web/tsconfig.json +10 -0

Not shown: this page already shows 6000 lines of diff.

A web/vite.config.ts +14 -0

Not shown: this page already shows 6000 lines of diff.

A worker/package.json +22 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/auth.ts +188 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/db.ts +487 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/env.ts +20 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/examples.ts +63 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/index.ts +53 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/mcp/docs.ts +358 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/mcp/handler.test.ts +332 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/mcp/handler.ts +399 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/mcp/text-modules.d.ts +15 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/mcp/tsconfig.check.json +10 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/router.ts +314 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/runtime-validation.ts +83 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/service.ts +349 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/sweep.ts +53 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/util.ts +53 -0

Not shown: this page already shows 6000 lines of diff.

A worker/src/validate.ts +75 -0

Not shown: this page already shows 6000 lines of diff.

A worker/test/auth.test.ts +87 -0

Not shown: this page already shows 6000 lines of diff.

A worker/test/sweep.test.ts +57 -0

Not shown: this page already shows 6000 lines of diff.

A worker/test/util.test.ts +55 -0

Not shown: this page already shows 6000 lines of diff.

A worker/test/validate.test.ts +72 -0

Not shown: this page already shows 6000 lines of diff.

A worker/tsconfig.json +11 -0

Not shown: this page already shows 6000 lines of diff.

A wrangler.jsonc +86 -0

Not shown: this page already shows 6000 lines of diff.