From 81f57ea5ce781a25feecd0f5e4df970a065fb65c Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 12:00:45 +0800 Subject: [PATCH 1/7] chore: share game-studio hermes plugin --- .hermes/README.md | 41 ++++- .../game-studio/.codex-plugin/plugin.json | 46 ++++++ .hermes/plugins/game-studio/__init__.py | 38 +++++ .../plugins/game-studio/assets/app-icon.png | Bin 0 -> 8885 bytes .../game-studio/assets/game-studio.svg | 25 +++ .hermes/plugins/game-studio/plugin.yaml | 6 + .../references/alternative-3d-engines.md | 50 ++++++ .../references/engine-selection.md | 53 ++++++ .../references/frontend-prompts.md | 97 +++++++++++ .../references/gltf-loading-starter.md | 43 +++++ .../references/phaser-architecture.md | 56 +++++++ .../references/playtest-checklist.md | 51 ++++++ .../references/rapier-integration-starter.md | 42 +++++ .../references/react-three-fiber-stack.md | 42 +++++ .../references/react-three-fiber-starter.md | 51 ++++++ .../game-studio/references/sprite-pipeline.md | 57 +++++++ .../references/three-hud-layout-patterns.md | 61 +++++++ .../references/three-webgl-architecture.md | 61 +++++++ .../game-studio/references/threejs-stack.md | 41 +++++ .../references/threejs-vanilla-starter.md | 58 +++++++ .../references/web-3d-asset-pipeline.md | 47 ++++++ .../webgl-debugging-and-performance.md | 36 +++++ .../scripts/build_sprite_edit_canvas.py | 86 ++++++++++ .../scripts/normalize_sprite_strip.py | 152 ++++++++++++++++++ .../scripts/render_sprite_preview_sheet.py | 99 ++++++++++++ .../game-studio/skills/game-playtest/SKILL.md | 76 +++++++++ .../skills/game-playtest/agents/openai.yaml | 4 + .../game-studio/skills/game-studio/SKILL.md | 94 +++++++++++ .../skills/game-studio/agents/openai.yaml | 4 + .../skills/game-ui-frontend/SKILL.md | 112 +++++++++++++ .../game-ui-frontend/agents/openai.yaml | 4 + .../skills/phaser-2d-game/SKILL.md | 88 ++++++++++ .../skills/phaser-2d-game/agents/openai.yaml | 4 + .../skills/react-three-fiber-game/SKILL.md | 87 ++++++++++ .../react-three-fiber-game/agents/openai.yaml | 4 + .../skills/sprite-pipeline/SKILL.md | 102 ++++++++++++ .../skills/sprite-pipeline/agents/openai.yaml | 4 + .../skills/three-webgl-game/SKILL.md | 124 ++++++++++++++ .../three-webgl-game/agents/openai.yaml | 4 + .../skills/web-3d-asset-pipeline/SKILL.md | 78 +++++++++ .../web-3d-asset-pipeline/agents/openai.yaml | 4 + .../skills/web-game-foundations/SKILL.md | 95 +++++++++++ .../web-game-foundations/agents/openai.yaml | 4 + 43 files changed, 2230 insertions(+), 1 deletion(-) create mode 100644 .hermes/plugins/game-studio/.codex-plugin/plugin.json create mode 100644 .hermes/plugins/game-studio/__init__.py create mode 100644 .hermes/plugins/game-studio/assets/app-icon.png create mode 100644 .hermes/plugins/game-studio/assets/game-studio.svg create mode 100644 .hermes/plugins/game-studio/plugin.yaml create mode 100644 .hermes/plugins/game-studio/references/alternative-3d-engines.md create mode 100644 .hermes/plugins/game-studio/references/engine-selection.md create mode 100644 .hermes/plugins/game-studio/references/frontend-prompts.md create mode 100644 .hermes/plugins/game-studio/references/gltf-loading-starter.md create mode 100644 .hermes/plugins/game-studio/references/phaser-architecture.md create mode 100644 .hermes/plugins/game-studio/references/playtest-checklist.md create mode 100644 .hermes/plugins/game-studio/references/rapier-integration-starter.md create mode 100644 .hermes/plugins/game-studio/references/react-three-fiber-stack.md create mode 100644 .hermes/plugins/game-studio/references/react-three-fiber-starter.md create mode 100644 .hermes/plugins/game-studio/references/sprite-pipeline.md create mode 100644 .hermes/plugins/game-studio/references/three-hud-layout-patterns.md create mode 100644 .hermes/plugins/game-studio/references/three-webgl-architecture.md create mode 100644 .hermes/plugins/game-studio/references/threejs-stack.md create mode 100644 .hermes/plugins/game-studio/references/threejs-vanilla-starter.md create mode 100644 .hermes/plugins/game-studio/references/web-3d-asset-pipeline.md create mode 100644 .hermes/plugins/game-studio/references/webgl-debugging-and-performance.md create mode 100644 .hermes/plugins/game-studio/scripts/build_sprite_edit_canvas.py create mode 100644 .hermes/plugins/game-studio/scripts/normalize_sprite_strip.py create mode 100644 .hermes/plugins/game-studio/scripts/render_sprite_preview_sheet.py create mode 100644 .hermes/plugins/game-studio/skills/game-playtest/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/game-playtest/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/game-studio/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/game-studio/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/game-ui-frontend/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/game-ui-frontend/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/phaser-2d-game/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/phaser-2d-game/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/react-three-fiber-game/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/react-three-fiber-game/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/sprite-pipeline/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/sprite-pipeline/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/three-webgl-game/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/three-webgl-game/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/web-3d-asset-pipeline/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/web-3d-asset-pipeline/agents/openai.yaml create mode 100644 .hermes/plugins/game-studio/skills/web-game-foundations/SKILL.md create mode 100644 .hermes/plugins/game-studio/skills/web-game-foundations/agents/openai.yaml diff --git a/.hermes/README.md b/.hermes/README.md index f88c5076..b5cdaf6a 100644 --- a/.hermes/README.md +++ b/.hermes/README.md @@ -24,9 +24,47 @@ │ ├─ pitfalls.md # 踩坑与排障记录 │ └─ handoff-template.md # 任务交接模板 ├─ plans/ # 阶段性计划与实施方案 -└─ skills/ # 未来可沉淀的仓库级 Hermes skills +├─ skills/ # 仓库级 Hermes skills +└─ plugins/ # 仓库级 Hermes plugins(需显式启用项目 plugin) ``` +## 仓库级 Plugins + +本仓库可共享的 Hermes plugin 放在 `.hermes/plugins//`。当前已包含: + +- `.hermes/plugins/game-studio/`:浏览器游戏设计、原型、2D/3D 技术栈、素材管线与 playtest 相关工作流。 + +Hermes 的项目级 plugin 默认不会自动加载。团队成员拉取仓库后,如需使用本仓库内 plugin,请在仓库根目录启动 Hermes 前设置: + +```bash +export HERMES_ENABLE_PROJECT_PLUGINS=1 +``` + +然后确认当前 Hermes 配置的 `plugins.enabled` 中包含 `game-studio`。如果成员本机尚未启用过该 plugin,当前 Hermes 的 `hermes plugins enable` 只识别用户级或内置 plugin,可能不会识别项目级 plugin;可用以下命令写入个人配置: + +```bash +python - <<'PY' +from hermes_cli.config import load_config, save_config +config = load_config() +plugins = config.setdefault('plugins', {}) +enabled = set(plugins.get('enabled') or []) +disabled = set(plugins.get('disabled') or []) +enabled.add('game-studio') +disabled.discard('game-studio') +plugins['enabled'] = sorted(enabled) +plugins['disabled'] = sorted(disabled) +save_config(config) +PY +``` + +启用后重新进入一个新 Hermes 会话。`hermes plugins list` 当前主要展示内置和用户级 plugin,未必列出项目级 plugin;如需验证项目级扫描,可在仓库根目录运行: + +```bash +HERMES_ENABLE_PROJECT_PLUGINS=1 HERMES_PLUGINS_DEBUG=1 hermes chat -q "请读取 game-studio:game-studio skill 并概括它的用途" +``` + +该 plugin 注册的是带命名空间的 plugin skills,可用类似 `game-studio:phaser-2d-game` 的名称显式加载。 + ## 推荐给 Hermes 的启动提示 在本仓库中开始复杂任务时,可以先对 Hermes 说: @@ -51,3 +89,4 @@ - 大段临时聊天记录 - 尚未确认的一次性猜测 - 构建产物、日志、缓存、数据库 dump + diff --git a/.hermes/plugins/game-studio/.codex-plugin/plugin.json b/.hermes/plugins/game-studio/.codex-plugin/plugin.json new file mode 100644 index 00000000..dfae2450 --- /dev/null +++ b/.hermes/plugins/game-studio/.codex-plugin/plugin.json @@ -0,0 +1,46 @@ +{ + "name": "game-studio", + "version": "0.1.0", + "description": "Design, prototype, and ship browser games with guided 2D and 3D workflows, asset pipelines, and playtesting support.", + "author": { + "name": "OpenAI", + "email": "support@openai.com", + "url": "https://openai.com/" + }, + "homepage": "https://openai.com/", + "repository": "https://github.com/openai/plugins", + "license": "MIT", + "keywords": [ + "games", + "phaser", + "threejs", + "react-three-fiber", + "gltf", + "rapier", + "webgl", + "sprites", + "playtest" + ], + "skills": "./skills/", + "interface": { + "displayName": "Game Studio", + "shortDescription": "Design, prototype, and ship browser games", + "longDescription": "Plan, prototype, and build browser games with guided workflows for gameplay systems, UI, asset pipelines, and playtesting across 2D and 3D projects.", + "developerName": "OpenAI", + "category": "Coding", + "capabilities": [ + "Interactive", + "Write" + ], + "websiteURL": "https://openai.com/", + "privacyPolicyURL": "https://openai.com/policies/privacy-policy/", + "termsOfServiceURL": "https://openai.com/policies/terms-of-use/", + "defaultPrompt": [ + "Design a browser game and plan the core loop" + ], + "brandColor": "#0F766E", + "composerIcon": "./assets/game-studio.svg", + "logo": "./assets/app-icon.png", + "screenshots": [] + } +} diff --git a/.hermes/plugins/game-studio/__init__.py b/.hermes/plugins/game-studio/__init__.py new file mode 100644 index 00000000..bcf37e07 --- /dev/null +++ b/.hermes/plugins/game-studio/__init__.py @@ -0,0 +1,38 @@ +"""Hermes wrapper for the OpenAI Codex Game Studio plugin. + +This plugin was imported from a Codex curated plugin cache. It exposes the +plugin's bundled SKILL.md files as Hermes plugin skills using qualified names +like `game-studio:phaser-2d-game`. +""" + +from __future__ import annotations + +from pathlib import Path + + +def _read_description(skill_md: Path) -> str: + try: + text = skill_md.read_text(encoding="utf-8")[:4000] + except Exception: + return "" + if text.startswith("---"): + end = text.find("\n---", 3) + if end != -1: + frontmatter = text[3:end] + for line in frontmatter.splitlines(): + if line.strip().startswith("description:"): + return line.split(":", 1)[1].strip().strip("\"'") + return "" + + +def register(ctx) -> None: + root = Path(__file__).resolve().parent + skills_root = root / "skills" + if not skills_root.exists(): + return + for skill_md in sorted(skills_root.glob("*/SKILL.md")): + ctx.register_skill( + name=skill_md.parent.name, + path=skill_md, + description=_read_description(skill_md), + ) diff --git a/.hermes/plugins/game-studio/assets/app-icon.png b/.hermes/plugins/game-studio/assets/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6415b68216c22c88ea1bb006e814836a6ee511c0 GIT binary patch literal 8885 zcmd6NhgTEN`*r9|dQ(&gh@cQU0#ZYh&;%4n=)HHO*U$rqV2~ysDWOP5s&oWIs`P-; zdv8K$p}paI&ig<7_MAOucV^G*GqdyDz4y+ zfwzkd0stIAs!H;@et3IxZqCM2{u}@7qDAOVvXQFOwFRGrQ}*;dEuTX`PBpQ`mR5BJ zPkbyJ=25RW+~yg&UFq|{OqYeJN0!W#+JE-OygW0e#fA2N z^Z0|L&!233x6xEsSlEO)R>&POyS+B%|JpwF6r5???Bn7!RLN~?Ys-bjVqXyzE5nu4 zx^i`SPBxA&_V#~IADH@1hUc2eZ6A=CM@n(2*hLL~3;g2SE?y?T!tWYARcR*2j-bM$ z3lp&$a;_xmmf266nR}=u_ca{s*kBdV`FNZK#KPmY-7`NwZxQA|h!8R~_%yJ#w?7^e z`3(@g8qj@2o)RZRh`_V5vWnR6y54^Q3&cxbND6FS<}ZmZ((Zf$#xIWgrC8DVM?DHp zRBMB0zbh&5R(fiDT!i!R1K?kgsY3(7EpH_8i0p~r*5>A)gK6VDd;!v=NVF|W;2KuL zN*lmgZkW=*G=(RR!WB=1uMFru1mCYK=pvatynb1Nw59>NtcT}?+S;x^_g&m)1aLJ7 zz61xBUXvz?u}c6hNs*@0Y71=%086xnmsZYcyw}aD*M1X@rxyi|=OAIeQ(B*V=(vb>p z3|KB{+;_?6(?~R?K4a+G+m`8=FXZ2$xmDH`ETQBF*eX2mn6i;L8j?B~v9Sj*_HuYR zR7j<_J)M=OvK|$26|2sE3}=1?2mOfZInBt(IMDG1{8d(W@~Hi`p3BF}XGKO0oWVR= zUOjyf;9mPpl>GpX@MkExJgjs=+Byc8cStVf^CEulgGVIa-(C@aYs;aM? zUBVB6l7h^xG=w9cHDVxK1WIY!!h$3tTp5%f2Y-Vd&-{1k0rq*u{NKMfH($Q&OVS|f zZ9ZtbbeGM(%>sZgH5-P0?cYt8KExm)WG`cFw}=8LRM%SLV{Jq5OJfih2ss@qfKG3Q zN>;?=g9af&RypYqu-Ip=AH7}dX7T0aJKIWGRc*kJ$~hSs8AYUJF%``^D?p*IQ<@Xa z2V!QetpE_}WX9w-rKC)gM@}*Vp1hgqSF?eSG~t^oe>O5GcD5M(|Cgm;%-?S8xhft& z(Ku-rGGnq`TnheELI_vh?>u!-&Cn%6Pz^bl;0da^2|oJ7psELN*nToj?Tcq_G_4<9 z$OSHv1;D;;Jd=T{s^!}vWMyST!U1^ZU-)}C`dji!4EHQ;01O`nU(M*L}7r0|?VUa0lzk&&>eBwzB{J7J%bPsZNu;wBl$wTssVsb=g>6c6+5 z{_c&vZ86#3Y$?S$>1r~NNByqapD;>-q^K3hL&Henf}*0)0#{0}H4{MRcA`oc?+&7Y zT{&Ns4Y;$(iO@wknl1VLjwf?x;9QR#r^0Afm_ZXoq{r^^@M^*_Nonp#NnO-Q^}9I& z18QV$R#w&=UG6i2NWK}oTdskFzQo}WyU{qSVo``}=nk80V;$q#TZ+6#C>-zzPypO+ zM)e-wo?*sgBV)$?_JzH@eH+ak%3+{etV{eg5 zj4{KG>CUqaI>f}p)-&}k;k#pD7YqMgwngT%d-fbPTU%|wt>slzV&?C+YH`q^??s}a zzh4HOe|z-OGoOXy_gKkl+vWZfi14=2*Bce1)UA3osNHqQLUOfqyFzu2F~8gX&d$RW zh|&uJk0qgJ4bNTP%G_QZA7OVY1)fW9hxT+tkgTDhX5}Ftc1L(aDVB1*y!BJp*`pNL zOUgAZAEL(FnqpUa5}{pPl&krVKRlM=5@#bZo?q0C_1PxE#L?vbzSWkWg3SvmG9+|9cL1lnzEPleK=djtg7Qb35U3QRiw zYDh;*P}anYGB0oDF}u~cJ$lUZJhV>p+VO?`khc&K>2ad}rVMM_j9QcEx$W5}pzI1X zlrPnQx%-I!S!zIn~od1YnVq$^HXlH@hYG@AYoFk0y`W*=YvyUB4w`q;74B_D0EGScZ^3jAlN5PA&&8Q%N}I( z7O|h9u8#J`VzxqrhJxS?7b@*PIHNbWJ}%jP%+v!hk#tKO<1MeV=>m@>LT_3Cd(~n{ z-peeT@9O$L4_DFmZGrbj@TkkO|7)I`N=w-Ye(THSi=&*#aURhjc*OAtT&ppRB8qr=17y6zL793F6fJw)mL+ z(){?^e4AWG7UbI$-Lv6#t=L!n;RDq;^XMQLFbg(%MSU71v?;wRH+vMA;bMYzAbMkz z;E4PSu%K54f?7pnCZt!#ze}ff+cY2Zq^Mn6>dL<#(_}EglEb@AKx?aC1|FjmYv$@b zf3@J)HK=%7d+9&&eC3w0h|2LB}!CE8^ zBF>xZi1F$ zrEZ#Aggmt8C2p-8#9ME1Lrr2#H1>BS``|n+^rMX&R_DYj@93Zv8tm|==xFGR=ZQ$R z5F2NzxL)leVA&7+)c{iIGcDfIrO}dr^q|XZXg~Cz5qRd6_8EKNdk2W>Y?Z`)a;Dgf zTD3Haow&+ZF>C5%AEFrM{qnHYtemOMLJ20n2nEH&d|tn~&w-^W^pQq5SX(PaM@Mtk z3IuPdk5M^IRWQzOemwvsS5j9xG%VXsKJhjAC~&pTXkcRBR`D)mwGxyS>7 z>!zjK*V#M`-sm1<^uOY0UaGpQ=7p5yTty@yB{d~2C4B@}KtO;7zlz8MV3TvXF@JV4 zgTxlLx+jsXQdZ8`OuF06VjxW*Eulh1MomOUze5Eg4~(WT*I;YMlhZ7wz0!r z!_23V0fB-E?5NVRHV@IOSR;p|EnbFJtd?V)UQI2z!S1?}S(%SbdRQ$43ZrGcvG)tQ zyaHB+X6nu}@mHDe6Z zERS-HpzpS(Fk_IFGyB^3`u7UoE7r&KQ==;TmJ8$7@s-4NK0!8BK@<@t@m?JYhD^Mh zCQD3F_t%XYrn3{HQVm_+D!&~hf`BrgIl6BOzl}^zeh-)ka7V!4{3&O*0%j=az>OL)4Q#THH4-x$iK zV(YoIcvV;@zOtF`dO zOT@{cHu1WM$ycrHeP%@sB1oh02TOR!&6R)HVqK}6l3v!D}eqnT@5SL8%y=>3r&zT5_I=&FY15niy%rB(-Wl!Rym_!;<&UY zb;B_#x|+}hUp-LsMk?}L_;`sr-p1yM9un6q-)}I=FpTjsmY1?Nm@$wD#ceI0VJu2&hGO`3gFS%Pl(Gta7K>rotmVi%XwUR0KK z33~I51Q;{e^M*hOkj$#28OxpyJg?|C59Tr*eh1rfU=|C*qi9uT=6&)TUlL5j1qecTkV5 zU+v)l?oDm6sCoLM^OZzsDJ^H-jm+zJbziynRrbl74YKPiZtRWDftewz9~aBZkj;V< zsh^egaUXyd=>&S)8_B;8m!g(Y%>5L7Ev+DWKH=3v7k8z<8mGCNdolI5VITmk| zZFv=f{m{Nq0PV7x@)z*^BCeQ?|L1}^bm>I?H47`$*zdstyI-e?c6r*5GD66s86zvyGihernYYq<-($Z_0E5h7nja$9$qOen1NL-%jtT@x})9{0ac5mNCy7*YB&8E z+m$;NjQXlJ%9Wp{FzWt$>)Vx?OCm~Mr>GQ_dou&fk%SD=U@v;*sbMLO2RT~8 z6HuuVx?ujxrLLH#wJL1Ex`qYEl)rNrurx05Nn)w?lrpO#0GICGn(`zZ&@hosk6reRV6 zfq^kkbHWJ=GoLlNMOw>CX?@bq8W@^8?;Fd-a=T{wuvfn^4Q>vm9vUlIDV5>}IaKme zeqe0JhN7VrUd3Ctrd@@s5^sI4>Wx{?TQiAOz5Odo@7d1&tKU_Tb98!vDs3yAd;fdc zv9x0ZNwo~(p%v`v$Gv81GleHn|tg=&Y;!GiC0 zWohMr5$ikoqd%IrE%oUbJ-?NN)Ci@BmFk9CM!0FSi4Jy%|0AITPpxi_<)IL9?#pbJfu~1={D&&z|;4ZKG0FPXDQX zz9zvfiI5iTg=MW~fl>8NVM6L@y&(_9SvZU|X|rAsPhkw$=(09(DSnyopD+8HA-#xv zS>Dm+={imCo&JneHdUhrcZEsTiyB7+r|RuZMdv{ zdo%%N1L*?g-8kQS6}-ZEwX_#Jj3iW{!u-d`1os@tQaHMknjDtIiRzHRC)!fiOY}nmHvMKzeTT zwqgD3`4qESZ#Sd^KT?wc&j@3{dMB#16t zj$yJ;Li;q>{`rvE`Vgk^?pa~5y+vJ4O-(R0vmuWHNJf%_Ea}5@d~5!4u;V3p05d1} zk5ZL46YFjN6Z<8j3NHiqyf?)Y0(gXEI1=vsC96dd3LqpkSKRwoe{*X>76EXG$jV|R za~MeF!$0>bZ+PRLryYR-;&pPx?a^Wh+k_OU4^*4gJW_t`Q^fwG{b(__tWgONSSWJNdE_61}@m3JTp?r!a!oCI8?Q zd_UDd2Y0>bYry$hbkwxh;kcCI$; zKm);TN`T{FptOMv!ug4a)z!Xa^sw72k|P}H=8zD6olRN2&vv7$(Cl{>`mtCl#AxLH z)UWB$nHecimLDYzH27Wx28fp=pPOF(yS5s=JRCVf&<%5E_jZvP+}V3lUzGie5OWwv6KT zC?^aWJ8U?2yRFIbZxh$U7>0d|<)C59PU(Hh{HW>Eef5<~S*e8qKZ9T*C5Yl`tJbxR zWoa(vK<4&eB`s_D4s&7<^=Ks-yvREX>jX^*`pP#DRLKbKOIh&KuE5B_TIpHKdD8|m zFL9Q4H-Xz-4mI7((&8nNvv7WC$KJld;mc<|!%$yuUz6~>>skNPcXM-d0`VmCw!o%X z)Xp#&+-u}g1XGaGTYWZS6{ML>W+Jw2u9_Lvx?BQwL##dw`eK8j(AWqEH zyP*bhyPFYUNxkoK#ZhFP#}9zAF`GCU2ahax0fvPU0mzOUi&4XX+$h}TIJwf2OaGo}>MrYQtYIO$@fWyI_?&d^*1rZbAd`f`qm=;+34e+DAB41oj zLll?zw17@k8~`d2+(8WRzq{h5C}lVR3{-V1Nw@!Bi;>Bg!njysT>j#NB~%60G@Jex zUguUCp%4Kc9HZv-+|3ei*@_zi__2&zuvn%@x&t!+JO??O@*POlx8`Z$3V-s0PP1WU z!^Zbf>y{}!QXZ?FGfjgD(HBJ##p(fL?`VAFM>Q6v%fNtuEZUZX!3>c=6NEG$NJBmu z86;_eRP>_q677DxnYZTYPqO5ZhclZAu&tv99|6B(1;9n%k^ESkkv?~)7{mRy&R(^v zc($q$uq^XTNj+u9wSS4CZzZ7HTIQF?;*@r+IF*?efaBGEqxrAirdIihJ*2!_oD)o7 zjBYZeTVC-8{`l>#JSW{|XAePMTIhrUpOqvQcahUa8~ZbAV~a1bHh{hw5&kqbwxv?%y|u7ZQz28p27V0rG+s!7n)NRdcl^ zfIm2RQu_T>N%6w9qwUS=T(7YwO>|BWIFXl^*MG^8oQ)}cMF0x-o}PE%KDh}|z#=e> zQWeaaNGEe*8a7KG?%x<+XQrxvECbVBqG-Px^ZVvZ2FO1g`PHe(d1{)6FHc%)Je|ns zlAhLV{xBty)1Mn#1bDaUT-My2d8(ILRY%6OJpb;W9*~f9J$ZN|hA`Is;3X5+A~9}= zb?K1S6!E3(vv4%D&%%j*kh^c~D3%CPtjoe=&g5c-HspftsB&z!nVNKPs>yf>cg_!ue_ci!B{MV-Ri;_O`C-BDsLYd8oL~$_k zW|@{6R}!bsUV_Y09`6ii-fh{Tmcl4(p;GcoZ8XEl&GysXc{PD3Gf$ADTIH238XjEUa}GZVXGeentW>Eu0GF)Kk^Q6`Z#>}23o>bV$_l)PsaDUv>^PKuG#~hP&1v|h z*)YLUbmu+G6(#y2fXJ_ntaqmP`NsTgG}hz~xu>-+AKW7o?%J3gRfkVZ-&YI=E%i^4 z@^m==yEs0*fEcA7+eYfMwYIjB5OOiZE1UFoBIoZNnp^}@Bu$@^Kl=F>d}0A{NgqPa zJ0D`_kC}jFqg?koj%k46~r@O4t2zmZcut*k-+E%*8;NajcQ$GGd z`=+!*!I3yEu+Ht*24Pi>cM3;s^|DU$M%Q4?z9u1JOpjfXfiB&45AZ5*BBZdaUwE*y zq_lC65V5_#KdEP+h@T8%{b8T2kb4+;^PY%FZ|2t|Pv~1b)U5o~ODfCE|5Z+xKwO@R zPzx2lDVDbQ*J{TP#sDYj8QCpAWOO`6j^lo>yYqpw1+EB+#Ov?#0i}JXs78QJ0E$Ct zcZ49lQN?E>%TB9w3~uPROP{APOH!S52j;$BUXMH@W+-DE%8CC(!H$^Ya#yRL}4%kUwM7WJeX;_u^%yTetzDy zZ@J3Id>bD=otKD~LYy~7Kw(hurs{ChJk{_ikGzw$Z2+Y;#y#dMAKgh1eUdv0-ONQGG~ zC2rx32dukiKHr1v^4DWyAFStu;D3Q%p!H!g4&HL5`uY6eZBiul$fDora(Dc2ey(%0 z3AT-wfjn@I>^L5`dNuu@Rj9N2yUdT%fZ|L>PU=Q8AJuxAN2DAp^1`~!56 z6-=BF`{t9sm+IAy7E`6Z}u&uAJMJ@8xDGFcU9z8&7Eu0;un;0b3+<(z;g*WR6d zUEA)x4t+nFDoS0&$;^c2U&aKVX~&fy+4~N=4Y>%c#VF~01?6^STNhf1pYkj2pM0ia zL1^p+i*L%JCy#P|Dk@s`2JclGH@GjYl|j!lsFVJ_{!e1G&i41`4z&VQc}3vQ$*^*g z5?Q`|&9l1J-3q^G2Q79=Oz`)B6UROBd;KRAFRy3&TdG;^dtNKJq*K&UbAVkHDnA`v z1hq?f?8ukrqDXw+keo+Skn9yWYHx0^Fy$4zaY*m!<4|{GR6A*1pQpI4qiDhgR_;0S zEp7)Uo|&wWCOsW1EgTves<_*5AT`oF+X21ytGU&u9-96d@Z&XZ3&3b`aWNSW`QkdJ z^}8dwWc#urRer5hp + Game Studio + A stylized browser game plugin icon with a viewport frame, a d-pad, and layered tiles. + + + + + + + + + + + + + + + + + + + + + + diff --git a/.hermes/plugins/game-studio/plugin.yaml b/.hermes/plugins/game-studio/plugin.yaml new file mode 100644 index 00000000..6d20c1c9 --- /dev/null +++ b/.hermes/plugins/game-studio/plugin.yaml @@ -0,0 +1,6 @@ +name: game-studio +version: 0.1.0 +description: Design, prototype, and ship browser games with guided 2D and 3D workflows, + asset pipelines, and playtesting support. +author: OpenAI +kind: standalone diff --git a/.hermes/plugins/game-studio/references/alternative-3d-engines.md b/.hermes/plugins/game-studio/references/alternative-3d-engines.md new file mode 100644 index 00000000..14c4192d --- /dev/null +++ b/.hermes/plugins/game-studio/references/alternative-3d-engines.md @@ -0,0 +1,50 @@ +# Alternative 3D Engines + +This plugin defaults to Three.js and React Three Fiber for code generation. Babylon.js and PlayCanvas still matter, but they are reference-only alternatives in the current plugin shape. + +## Babylon.js + +Useful sources: + +- [Babylon.js home](https://babylonjs.com/) +- [Engine specifications](https://www.babylonjs.com/specifications/) +- [Babylon.js Editor](https://editor.babylonjs.com/) + +Choose Babylon.js when: + +- the user explicitly wants Babylon.js +- the team wants a more engine-heavy stack with scene, material, viewer, and editor tooling built around one ecosystem +- WebGPU, Havok, node-based rendering or material tooling, or Babylon-specific runtime features are part of the reason for the choice + +What Babylon.js is good at: + +- full-engine 3D workflows +- strong built-in tooling and editor surfaces +- WebGL and WebGPU support inside one ecosystem +- integrated viewer and inspection-oriented workflows + +## PlayCanvas + +Useful sources: + +- [PlayCanvas graphics overview](https://developer.playcanvas.com/user-manual/graphics/) +- [Supported formats](https://developer.playcanvas.com/user-manual/assets/supported-formats/) +- [PlayCanvas React GLTF API](https://developer.playcanvas.com/user-manual/react/api/gltf/) +- [PlayCanvas Web Components](https://developer.playcanvas.com/user-manual/web-components/) + +Choose PlayCanvas when: + +- the user explicitly wants PlayCanvas +- the team prefers an editor-centric browser engine workflow +- GLB import, runtime tooling, React bindings, or web-component-based embedding are central to the project + +What PlayCanvas is good at: + +- editor and engine working together +- GLB-centric browser asset workflows +- strong web embedding patterns +- WebGL and WebGPU support with browser-focused runtime tooling + +## Default recommendation + +If the user has not already chosen Babylon.js or PlayCanvas, prefer Three.js or React Three Fiber in this plugin because they give the best balance of portability, ecosystem depth, and predictable code generation across normal browser-game repos. diff --git a/.hermes/plugins/game-studio/references/engine-selection.md b/.hermes/plugins/game-studio/references/engine-selection.md new file mode 100644 index 00000000..d5d7bc10 --- /dev/null +++ b/.hermes/plugins/game-studio/references/engine-selection.md @@ -0,0 +1,53 @@ +# Engine Selection + +Use this table to choose the default implementation path for browser games in this plugin. + +## Default choices + +- Choose Phaser for 2D games unless the user explicitly asks for another stack. +- Choose vanilla Three.js for explicit 3D or WebGL-first experiences in plain TypeScript or Vite apps. +- Choose React Three Fiber when the 3D scene is part of a React application and shared app state or declarative composition matters. +- Choose raw WebGL only when engine abstractions are the problem, not because WebGL sounds more advanced. +- Treat Babylon.js and PlayCanvas as alternative ecosystems, not the default path in this plugin. + +## Phaser is the best fit when + +- the game is sprite- or tile-based +- the game is top-down, side-view, or grid tactics +- you need camera, sprite, and scene primitives quickly +- the UI will mostly live in DOM overlays +- the game loop is gameplay-first rather than renderer-first + +## Three.js is the best fit when + +- the game is genuinely 3D +- camera movement and depth are central to play +- materials, lighting, or scene composition matter more than sprite tooling +- the user explicitly asks for Three.js or WebGL-based 3D work +- the team wants direct control over scene setup, loaders, physics integration, and the game loop + +## React Three Fiber is the best fit when + +- the project already lives in React +- the 3D scene needs to share app state with the rest of the product +- declarative scene composition is more valuable than a fully imperative loop +- the team benefits from pmndrs tooling such as Drei, React Postprocessing, or `@react-three/rapier` + +## Babylon.js or PlayCanvas are the best fit when + +- the user explicitly asks for those engines +- the team already has engine-specific tooling or editor workflows in those ecosystems +- the project wants engine-heavy runtime features, editor-first workflows, or platform-specific tooling that Three.js does not provide by default + +## Raw WebGL is the best fit when + +- the project is shader-heavy +- you need a custom renderer or post-processing pipeline +- the user explicitly wants low-level rendering control + +## Avoid these mismatches + +- Do not choose a 3D stack for a normal 2D tactics or platformer game. +- Do not choose raw WebGL for a game that mostly needs engine conveniences. +- Do not force HUD and menus into canvas or WebGL when DOM would be clearer. +- Do not default to Babylon.js or PlayCanvas when the user mainly wants portable TypeScript code generation across browser-game repos. diff --git a/.hermes/plugins/game-studio/references/frontend-prompts.md b/.hermes/plugins/game-studio/references/frontend-prompts.md new file mode 100644 index 00000000..86d46e2d --- /dev/null +++ b/.hermes/plugins/game-studio/references/frontend-prompts.md @@ -0,0 +1,97 @@ +# Frontend Prompts + +Use these prompt shapes to keep browser-game UI intentional instead of generic. + +## Prompt ingredients + +- game genre and fantasy +- camera or viewpoint +- player verbs +- HUD zones +- menu surfaces +- motion tone +- desktop and mobile expectations +- playfield protection and disclosure strategy +- anti-patterns to avoid + +## HUD implementation prompt + +```text +Design and implement the HUD for a browser game. + +Game fantasy: +Viewpoint: +Primary verbs: +HUD zones: +Tone: +Motion: +Platforms: desktop and mobile +Constraints: readable over active gameplay, DOM-based overlays, CSS variables, no generic dashboard look +Playfield protection: keep the central play area clear during normal play, prefer one primary persistent HUD cluster, and move long-form notes or controls behind menus +Avoid: flat admin UI, default font stack, cluttered overlays, constant micro-animation, equal-weight cards around every edge, broad always-on panels that cover the world +``` + +## Menu implementation prompt + +```text +Build the shell UI for a browser game with the following surfaces: +- title screen +- pause menu +- settings panel +- game-over or victory screen + +Keep the menus visually tied to the game world, not to a SaaS app aesthetic. Use strong hierarchy, intentional typography, meaningful motion, and responsive layout. +``` + +## Low-chrome 3D starter prompt + +```text +Design the initial playable HUD for a browser 3D game. + +Goal: the first screen should feel playable in under 3 seconds, not like a dashboard. +Camera mode: +Primary verbs: +Persistent UI budget: +- one compact objective chip or status cluster +- one optional small secondary surface +- one transient controls or interaction hint + +Interaction rules: +- keep the center of the playfield clear +- keep the lower-middle playfield mostly clear during normal play +- lore, notes, quest details, and long control lists live behind a drawer, pause menu, or toggle +- modal and pause states must gate camera input correctly + +Avoid: +- giant title cards over live gameplay +- field notes, controls, and objectives all open at once +- equally weighted glass panels in every corner +- full-screen overlay chrome during normal movement +``` + +## 3D overlay prompt + +```text +Design and implement the HUD and menu overlays for a browser 3D game. + +Engine context: +Camera mode: +Primary verbs: +Overlay surfaces: +Interaction constraints: +- DOM overlays, not in-scene UI by default +- modal and menu states must suspend or gate camera input correctly +- keyboard and pointer states must be explicit +- reduced-motion support for non-essential transitions +- keep the center of the screen clear during normal play +- keep the lower-middle playfield mostly clear during normal play +- start with one compact objective surface and transient hints rather than multiple permanent cards +- secondary content such as notes, lore, and full control references should be collapsed by default +Avoid: +- dashboard UI +- cluttered full-screen overlays +- boxed panels around every edge of the viewport +- full-width top-and-bottom panel stacks +- permanent text-heavy cards competing with the scene +- camera movement continuing under active menus +``` diff --git a/.hermes/plugins/game-studio/references/gltf-loading-starter.md b/.hermes/plugins/game-studio/references/gltf-loading-starter.md new file mode 100644 index 00000000..2eb1324c --- /dev/null +++ b/.hermes/plugins/game-studio/references/gltf-loading-starter.md @@ -0,0 +1,43 @@ +# GLB Loading Starter + +Use this as the canonical minimal pattern for loading shipped 3D content. + +## Vanilla Three.js + +```ts +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; +import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; + +const draco = new DRACOLoader(); +draco.setDecoderPath("/draco/"); + +const gltfLoader = new GLTFLoader(); +gltfLoader.setDRACOLoader(draco); + +gltfLoader.load("/assets/hero.glb", (gltf) => { + const root = gltf.scene; + root.traverse((node) => { + if ("castShadow" in node) { + node.castShadow = true; + node.receiveShadow = true; + } + }); + scene.add(root); +}); +``` + +## React Three Fiber + +```tsx +import { useGLTF } from "@react-three/drei"; + +function HeroModel() { + const gltf = useGLTF("/assets/hero.glb"); + return ; +} +``` + +## Notes + +- Default shipping format is GLB or glTF 2.0. +- Keep optimization upstream in the asset pipeline; loader code should stay boring. diff --git a/.hermes/plugins/game-studio/references/phaser-architecture.md b/.hermes/plugins/game-studio/references/phaser-architecture.md new file mode 100644 index 00000000..53fb903d --- /dev/null +++ b/.hermes/plugins/game-studio/references/phaser-architecture.md @@ -0,0 +1,56 @@ +# Phaser Architecture + +This is the default 2D structure for the plugin. + +## Recommended module split + +```text +src/ + game/ + simulation/ + state.ts + systems/ + rules/ + content/ + encounters/ + items/ + maps/ + input/ + actions.ts + bindings.ts + assets/ + manifest.ts + phaser/ + boot/ + scenes/ + BootScene.ts + MenuScene.ts + BattleScene.ts + view/ + sprites/ + fx/ + camera/ + adapters/ + sceneBridge.ts + ui/ + hud/ + menus/ + overlays/ +``` + +## Responsibilities + +- `simulation/`: source of truth for rules and saveable state +- `content/`: authored data and encounter configuration +- `input/`: action map and physical control bindings +- `assets/`: stable manifest keys and asset metadata +- `phaser/scenes/`: scene orchestration, not game rules +- `phaser/view/`: render and effect helpers +- `ui/`: DOM HUD, menus, and narrative panels + +## Rules + +- Phaser scenes read from and write to the simulation through a defined bridge. +- Game state changes should not depend on sprite or tween lifetime. +- Camera behavior should be isolated from combat or movement rules. +- Use DOM for dense text and settings surfaces. diff --git a/.hermes/plugins/game-studio/references/playtest-checklist.md b/.hermes/plugins/game-studio/references/playtest-checklist.md new file mode 100644 index 00000000..3a2e81d4 --- /dev/null +++ b/.hermes/plugins/game-studio/references/playtest-checklist.md @@ -0,0 +1,51 @@ +# Playtest Checklist + +Use this checklist for browser-game QA. + +## Universal checks + +- Does the game boot into a useful first state? +- Are the main verbs obvious and responsive? +- Does the HUD remain readable over gameplay? +- Does the first playable screen prioritize play over dashboard chrome? +- Does the central playfield stay mostly clear during normal play? +- Do pause, failure, and recovery states work? +- Does the game survive viewport changes? + +## 2D checks + +- sprite baseline consistency +- hit, hurt, and attack timing +- command menu focus and input state +- tile or platform readability +- particle or camera effects obscuring gameplay + +## 3D checks + +- camera control stability +- camera and menu-state handoff +- depth readability +- persistent overlay weight versus scene readability +- secondary notes, controls, and quest details collapsed by default +- resize and aspect-ratio handling +- renderer fallback or context-loss handling +- material and lighting stability across states +- GLB asset and texture streaming behavior +- collision proxy alignment +- GPU bottlenecks isolated with capture tools when needed + +## Browser checks + +- desktop and mobile viewports +- input modality differences +- reduced-motion behavior +- pause behavior when focus changes +- pointer-lock and camera-input release when overlays open +- transient onboarding hints dismiss or fade once the player is moving + +## Reporting + +- Capture screenshots for visual findings. +- Put findings in severity order. +- Include reproduction steps. +- Call out whether the likely owner is simulation, renderer, frontend, or asset pipeline. diff --git a/.hermes/plugins/game-studio/references/rapier-integration-starter.md b/.hermes/plugins/game-studio/references/rapier-integration-starter.md new file mode 100644 index 00000000..f78a5be3 --- /dev/null +++ b/.hermes/plugins/game-studio/references/rapier-integration-starter.md @@ -0,0 +1,42 @@ +# Rapier Integration Starter + +Use this as the smallest canonical pattern for adding physics without letting it take over the whole runtime. + +## Vanilla Three.js + +```ts +import RAPIER from "@dimforge/rapier3d-compat"; + +await RAPIER.init(); + +const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 }); +const body = world.createRigidBody(RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 2, 0)); +world.createCollider(RAPIER.ColliderDesc.cuboid(0.5, 0.5, 0.5), body); + +renderer.setAnimationLoop(() => { + world.step(); + const p = body.translation(); + mesh.position.set(p.x, p.y, p.z); + renderer.render(scene, camera); +}); +``` + +## React Three Fiber + +```tsx +import { Physics, RigidBody } from "@react-three/rapier"; + + + + + + + + +; +``` + +## Notes + +- Keep physics state synchronized through an explicit bridge. +- Do not bury gameplay rules inside render or physics callbacks. diff --git a/.hermes/plugins/game-studio/references/react-three-fiber-stack.md b/.hermes/plugins/game-studio/references/react-three-fiber-stack.md new file mode 100644 index 00000000..947a3e0d --- /dev/null +++ b/.hermes/plugins/game-studio/references/react-three-fiber-stack.md @@ -0,0 +1,42 @@ +# React Three Fiber Stack + +This is the default React-native 3D stack for the plugin. + +## Primary components + +- [React Three Fiber](https://r3f.docs.pmnd.rs/getting-started/introduction) for declarative Three.js rendering in React. +- [Drei](https://drei.docs.pmnd.rs/controls/introduction) for controls, loaders, helpers, environments, and common scene utilities. +- [React Postprocessing](https://react-postprocessing.docs.pmnd.rs/introduction) for effect composition in React-hosted scenes. +- [React Three Rapier](https://pmndrs.github.io/react-three-rapier/) for physics integration. +- [React Three A11y](https://a11y.docs.pmnd.rs/introduction) when scene interaction benefits from accessibility-aware patterns. +- [glTF Transform](https://gltf-transform.dev/) and the [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) for shipped assets. + +## Default stack choices + +- Runtime: `@react-three/fiber` + `three` +- Helper ecosystem: `@react-three/drei` +- Physics: `@react-three/rapier` +- Effects: `@react-three/postprocessing` +- Accessibility: `@react-three/a11y` when appropriate +- Assets: GLB or glTF 2.0 + +## Choose this stack when + +- the 3D scene lives inside a React app +- the UI shell, settings, or product flow already uses React +- the team benefits from declarative scene composition +- the scene must share app state with non-canvas UI + +## Avoid this stack when + +- the project wants a cleaner imperative loop with minimal React coordination +- the whole game runtime would be easier to reason about in plain TypeScript + +## Companion references + +- `threejs-stack.md` +- `react-three-fiber-starter.md` +- `gltf-loading-starter.md` +- `rapier-integration-starter.md` +- `web-3d-asset-pipeline.md` +- `webgl-debugging-and-performance.md` diff --git a/.hermes/plugins/game-studio/references/react-three-fiber-starter.md b/.hermes/plugins/game-studio/references/react-three-fiber-starter.md new file mode 100644 index 00000000..c14a92e8 --- /dev/null +++ b/.hermes/plugins/game-studio/references/react-three-fiber-starter.md @@ -0,0 +1,51 @@ +# React Three Fiber Starter + +Use this as the smallest canonical starting point for a React-hosted 3D scene. + +## Files + +```text +src/ + App.tsx +``` + +## `src/App.tsx` + +```tsx +import { Canvas } from "@react-three/fiber"; + +function Spinner() { + return ( + + + + + ); +} + +export default function App() { + return ( +
+ + + + + + +
+
Reach the lantern bridge.
+
WASD to move. Hold mouse to look.
+
+
+ ); +} +``` + +## Notes + +- Start here when the 3D scene lives inside an existing React app. +- Keep the initial HUD sparse. One compact objective surface and one transient hint is usually enough for a first playable scaffold. +- Put lore, notes, map, and settings behind drawers or modals instead of opening them all by default. +- Add GLB loading with `gltf-loading-starter.md`. +- Add physics with `rapier-integration-starter.md`. +- Use `three-hud-layout-patterns.md` for low-chrome 3D overlay defaults. diff --git a/.hermes/plugins/game-studio/references/sprite-pipeline.md b/.hermes/plugins/game-studio/references/sprite-pipeline.md new file mode 100644 index 00000000..d3804a62 --- /dev/null +++ b/.hermes/plugins/game-studio/references/sprite-pipeline.md @@ -0,0 +1,57 @@ +# Sprite Pipeline + +This is the default 2D animation workflow for the plugin. + +## Principles + +- Start from one approved in-game frame. +- Generate the animation as one strip, not isolated frames. +- Normalize the whole strip with one shared scale. +- Use one shared anchor, typically bottom-center. +- Preview before approving the asset. + +## Why this works + +- The approved seed frame preserves identity. +- Strip-first generation reduces frame-to-frame drift. +- Shared-scale normalization prevents one tall pose from making the character feel smaller. +- Locking frame 01 back to the shipped sprite preserves continuity for idle-to-action transitions. + +## Prompt template + +```text +Intended use: candidate production spritesheet for a 2D browser game animation review. +Edit the provided transparent reference canvas into a single horizontal -frame spritesheet. + +The existing sprite in the leftmost slot is the anchor frame and must remain the same character: +- same facing direction +- same silhouette family +- same palette family +- same proportions +- same readable face or key features +- same outfit details + +Composition: +- transparent canvas +- exactly one row of equal frame slots +- no extra characters +- no labels +- no scenery +- no poster layout + +Action: +- describe the specific animation beat from frame 1 through frame N + +Style: +- authentic pixel-art production asset +- crisp pixel clusters +- restrained palette +- not concept art +``` + +## Normalization notes + +- Use the union of detected sprite bounds per slot. +- Compute one scale from the largest detected frame and anchor. +- Bottom-align frames into the target canvas. +- Reuse the exact shipped frame for frame 01 when `--lock-frame1` is appropriate. diff --git a/.hermes/plugins/game-studio/references/three-hud-layout-patterns.md b/.hermes/plugins/game-studio/references/three-hud-layout-patterns.md new file mode 100644 index 00000000..af9a1345 --- /dev/null +++ b/.hermes/plugins/game-studio/references/three-hud-layout-patterns.md @@ -0,0 +1,61 @@ +# 3D HUD Layout Patterns + +Use these defaults for initial 3D browser-game scaffolds. The first screen should be playable before it is informational. + +## Layout Budget + +- Keep the center of the screen clear during normal play. +- On desktop, prefer one primary persistent cluster and one small secondary cluster. +- On mobile, prefer one compact persistent cluster and transient prompts. +- Secondary information belongs in drawers, toggles, pause menus, or contextual popovers. + +## Good Default Patterns + +### Objective chip + +- One short objective in a compact top-corner chip. +- One optional sublabel for location or mode. +- No giant hero banner over the live scene. + +### Contextual interaction prompt + +- Bottom-center or lower-corner pill. +- Appears only near interactables or during onboarding. +- Dismisses after first use or fades once the player is moving confidently. + +### Small status strip + +- Health, energy, party count, or beacon progress in a narrow edge-aligned strip. +- Use icons, short labels, and compact meters instead of stacked cards. + +### Collapsible journal or quest log + +- Closed by default. +- Opened by a hotkey, button, or pause state. +- Holds longer prose, lore, map notes, and multi-step objective details. + +### Pause and settings modal + +- Explicit modal state. +- Suspends pointer-lock, drag-look, or camera input while active. + +## Anti-Patterns + +- four to six glass cards permanently framing the viewport +- large lore or field-notes panels open during normal movement +- controls lists permanently pinned to the screen +- symmetric dashboard composition that competes with the scene +- oversized title panels staying visible after the first second of play + +## Example UI Budget + +- top-left: objective chip +- top-right: compact status strip +- bottom-center: transient interaction or controls hint +- pause menu or drawer: map, notes, inventory, settings + +## Prompt Add-On + +```text +Default to a low-chrome playable HUD. Keep the central playfield clear. Use one compact objective chip, one small status surface, and transient prompts. Put lore, field notes, full controls, and long checklists behind a drawer or pause menu. Avoid equal-weight boxed panels in every corner. +``` diff --git a/.hermes/plugins/game-studio/references/three-webgl-architecture.md b/.hermes/plugins/game-studio/references/three-webgl-architecture.md new file mode 100644 index 00000000..f6558b77 --- /dev/null +++ b/.hermes/plugins/game-studio/references/three-webgl-architecture.md @@ -0,0 +1,61 @@ +# Three WebGL Architecture + +This is the default 3D structure for the plugin. + +## Recommended module split + +```text +src/ + game/ + simulation/ + content/ + input/ + save/ + render/ + app/ + createRenderer.ts + createScene.ts + createCamera.ts + createLoop.ts + loaders/ + loadGltf.ts + loadEnvironment.ts + loadTextures.ts + objects/ + materials/ + lights/ + post/ + adapters/ + renderBridge.ts + physics/ + world.ts + colliders.ts + sync.ts + diagnostics/ + debugFlags.ts + perf.ts + ui/ + hud/ + menus/ + overlays/ +``` + +## Responsibilities + +- `simulation/`: rules, state, AI, progression, save data +- `render/app/`: renderer, scene, camera, resize, context lifecycle +- `render/loaders/`: GLTF, compression, texture, and environment loading +- `render/objects/`: scene graph construction and disposal +- `render/materials/`: material setup and shader boundaries +- `physics/`: Rapier world and simulation bridge +- `diagnostics/`: performance probes and GPU debugging hooks +- `ui/`: DOM HUD and menus + +## Rules + +- Scene graph objects are not the source of truth for game rules. +- Keep camera logic explicit and testable. +- Handle resize and context-loss as real browser concerns. +- Keep high-density UI in DOM even when the world is fully 3D. +- Treat GLB or glTF 2.0 as the default content format. +- Add physics and diagnostics as real subsystems, not temporary one-off utilities. diff --git a/.hermes/plugins/game-studio/references/threejs-stack.md b/.hermes/plugins/game-studio/references/threejs-stack.md new file mode 100644 index 00000000..aee34d4d --- /dev/null +++ b/.hermes/plugins/game-studio/references/threejs-stack.md @@ -0,0 +1,41 @@ +# Three.js Stack + +This is the default non-React 3D runtime stack for the plugin. + +## Primary components + +- [Three.js documentation](https://threejs.org/docs/) for the core renderer, scene graph, materials, cameras, loaders, and examples. +- [Rapier JavaScript guide](https://rapier.rs/docs/user_guides/javascript/getting_started_js/) for physics integration. +- [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) for the default shipping asset format. +- [glTF Transform](https://gltf-transform.dev/) for optimization, packaging, and compression workflows. +- [SpectorJS](https://spector.babylonjs.com/) for WebGL frame capture and GPU debugging. + +## Default stack choices + +- Runtime: `three` +- Tooling: TypeScript + Vite +- Assets: GLB or glTF 2.0 +- Loaders: `GLTFLoader`, `DRACOLoader`, `KTX2Loader` when the asset pipeline requires them +- Physics: Rapier JS +- UI: DOM overlays, not in-scene UI by default + +## Choose this stack when + +- the project is not React-first +- the team wants direct control over the render loop +- scene composition, loader setup, or custom render behavior needs imperative structure +- the game code should feel engine-like without a React abstraction layer + +## Avoid this stack when + +- the surrounding app is already React-heavy and wants shared declarative state +- the project needs an editor-first engine workflow more than a portable TypeScript runtime + +## Companion references + +- `three-webgl-architecture.md` +- `threejs-vanilla-starter.md` +- `gltf-loading-starter.md` +- `rapier-integration-starter.md` +- `web-3d-asset-pipeline.md` +- `webgl-debugging-and-performance.md` diff --git a/.hermes/plugins/game-studio/references/threejs-vanilla-starter.md b/.hermes/plugins/game-studio/references/threejs-vanilla-starter.md new file mode 100644 index 00000000..9f7830f6 --- /dev/null +++ b/.hermes/plugins/game-studio/references/threejs-vanilla-starter.md @@ -0,0 +1,58 @@ +# Three.js Vanilla Starter + +Use this as the smallest canonical starting point for a plain TypeScript or Vite Three.js app. + +## Files + +```text +src/ + main.ts +``` + +## `src/main.ts` + +```ts +import * as THREE from "three"; + +const scene = new THREE.Scene(); +scene.background = new THREE.Color("#101418"); + +const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200); +camera.position.set(0, 1.5, 4); + +const renderer = new THREE.WebGLRenderer({ antialias: true }); +renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); +renderer.setSize(window.innerWidth, window.innerHeight); +document.body.appendChild(renderer.domElement); + +scene.add(new THREE.AmbientLight(0xffffff, 0.7)); +const light = new THREE.DirectionalLight(0xffffff, 1.2); +light.position.set(4, 6, 3); +scene.add(light); + +const mesh = new THREE.Mesh( + new THREE.BoxGeometry(1, 1, 1), + new THREE.MeshStandardMaterial({ color: "#3dd9b8" }), +); +scene.add(mesh); + +window.addEventListener("resize", () => { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); +}); + +renderer.setAnimationLoop(() => { + mesh.rotation.y += 0.01; + renderer.render(scene, camera); +}); +``` + +## Notes + +- Start here for direct loop and renderer control. +- If the scaffold needs UI, start with one compact objective chip and one transient controls hint rather than multiple permanent cards. +- Keep notes, codex, maps, and settings behind on-demand surfaces. The starter scene should stay readable while moving the camera. +- Add GLB loading with `gltf-loading-starter.md`. +- Add physics sync with `rapier-integration-starter.md`. +- Use `three-hud-layout-patterns.md` for low-chrome 3D overlay defaults. diff --git a/.hermes/plugins/game-studio/references/web-3d-asset-pipeline.md b/.hermes/plugins/game-studio/references/web-3d-asset-pipeline.md new file mode 100644 index 00000000..ce5bc3da --- /dev/null +++ b/.hermes/plugins/game-studio/references/web-3d-asset-pipeline.md @@ -0,0 +1,47 @@ +# Web 3D Asset Pipeline + +This is the default 3D asset shipping guidance for the plugin. + +## Primary sources + +- [Blender glTF exporter manual](https://docs.blender.org/manual/en/latest/addons/import_export/scene_gltf2.html) +- [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) +- [glTF Transform](https://gltf-transform.dev/) +- [PlayCanvas supported formats](https://developer.playcanvas.com/user-manual/assets/supported-formats/) for a good reference on why GLB is the recommended runtime format in browser engines + +## Default output + +- Ship GLB when possible. +- Use `.gltf` with external files only when the asset pipeline or delivery strategy genuinely needs that shape. + +## Recommended workflow + +1. Clean the source asset in the DCC tool. +2. Export to GLB or glTF 2.0. +3. Run glTF Transform for validation, pruning, deduplication, and size reduction. +4. Apply the chosen geometry and texture compression strategy. +5. Verify pivots, scale, collision assumptions, and hierarchy naming. +6. Test the asset in the runtime before treating it as final. + +## Compression and optimization + +- Use Draco or Meshopt deliberately, not both by default. +- Use KTX2 or BasisU when the runtime stack supports GPU-friendly compressed textures. +- Keep texture resolution aligned with actual on-screen use. +- Reuse materials and avoid unnecessary texture uniqueness. + +## Runtime checks + +- scale is consistent across assets +- pivots match gameplay expectations +- node names are stable +- collision proxy needs are handled +- animation clips and variants load correctly +- memory and load time are reasonable for the scene + +## Starter patterns + +- `threejs-vanilla-starter.md` +- `react-three-fiber-starter.md` +- `gltf-loading-starter.md` +- `rapier-integration-starter.md` diff --git a/.hermes/plugins/game-studio/references/webgl-debugging-and-performance.md b/.hermes/plugins/game-studio/references/webgl-debugging-and-performance.md new file mode 100644 index 00000000..5d9853f7 --- /dev/null +++ b/.hermes/plugins/game-studio/references/webgl-debugging-and-performance.md @@ -0,0 +1,36 @@ +# WebGL Debugging and Performance + +Use this reference when a browser 3D scene is visually wrong, unstable, or slower than expected. + +## Primary tools + +- [SpectorJS](https://spector.babylonjs.com/) for frame capture, pipeline inspection, draw-call review, and shader debugging. +- Browser performance tooling for main-thread work, asset decode stalls, and memory pressure. +- Engine-native debug views and stats surfaces where available. + +## What to inspect first + +- draw-call count +- shader compilation churn +- texture memory pressure +- geometry count and material count +- post-processing cost +- asset decode and streaming stalls +- WebGL context loss or fallback behavior + +## Common causes of poor performance + +- too many unique materials +- oversized textures +- heavy GLB assets loaded without optimization +- complex post-processing on top of an already expensive scene +- physics and render state fighting for ownership +- React and scene state updating each other too frequently in React-hosted 3D apps + +## Debugging rules + +- Capture first, then guess. +- Reduce the scene until the perf cliff becomes obvious. +- Disable post-processing before rewriting core scene code. +- Verify the asset pipeline before blaming the renderer. +- Treat context-loss handling as a browser requirement, not an edge case. diff --git a/.hermes/plugins/game-studio/scripts/build_sprite_edit_canvas.py b/.hermes/plugins/game-studio/scripts/build_sprite_edit_canvas.py new file mode 100644 index 00000000..fc1b2a2e --- /dev/null +++ b/.hermes/plugins/game-studio/scripts/build_sprite_edit_canvas.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +"""Build a transparent edit canvas around a shipped seed sprite frame.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +try: + from PIL import Image +except ImportError as exc: # pragma: no cover + raise SystemExit( + "Pillow is required. Install it with `python3 -m pip install pillow`." + ) from exc + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Upscale a seed sprite with nearest-neighbor sampling and place it into " + "the leftmost slot of a larger transparent edit canvas." + ) + ) + parser.add_argument("--seed", required=True, help="Path to the approved seed frame.") + parser.add_argument("--out", required=True, help="Path to the output PNG.") + parser.add_argument( + "--frames", + type=int, + default=4, + help="Number of horizontal frame slots to reserve. Default: 4.", + ) + parser.add_argument( + "--slot-size", + type=int, + default=256, + help="Size of each square frame slot in pixels. Default: 256.", + ) + parser.add_argument( + "--canvas-size", + type=int, + default=1024, + help="Size of the square transparent canvas in pixels. Default: 1024.", + ) + return parser.parse_args() + + +def resize_seed(seed: Image.Image, slot_size: int) -> Image.Image: + max_dim = max(seed.size) + scale = slot_size / max_dim + if scale >= 1: + scale = max(1, int(scale)) + width = max(1, int(round(seed.width * scale))) + height = max(1, int(round(seed.height * scale))) + return seed.resize((width, height), Image.Resampling.NEAREST) + + +def main() -> None: + args = parse_args() + if args.frames < 1: + raise SystemExit("--frames must be at least 1.") + if args.slot_size < 1 or args.canvas_size < 1: + raise SystemExit("--slot-size and --canvas-size must be positive.") + + strip_width = args.frames * args.slot_size + if strip_width > args.canvas_size or args.slot_size > args.canvas_size: + raise SystemExit("Frame slots do not fit inside the requested canvas size.") + + seed = Image.open(args.seed).convert("RGBA") + seed = resize_seed(seed, args.slot_size) + + canvas = Image.new("RGBA", (args.canvas_size, args.canvas_size), (0, 0, 0, 0)) + strip_left = (args.canvas_size - strip_width) // 2 + strip_top = (args.canvas_size - args.slot_size) // 2 + slot_left = strip_left + slot_top = strip_top + paste_x = slot_left + (args.slot_size - seed.width) // 2 + paste_y = slot_top + (args.slot_size - seed.height) // 2 + canvas.alpha_composite(seed, (paste_x, paste_y)) + + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + canvas.save(out_path) + + +if __name__ == "__main__": + main() diff --git a/.hermes/plugins/game-studio/scripts/normalize_sprite_strip.py b/.hermes/plugins/game-studio/scripts/normalize_sprite_strip.py new file mode 100644 index 00000000..9bb3e048 --- /dev/null +++ b/.hermes/plugins/game-studio/scripts/normalize_sprite_strip.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +"""Normalize a raw animation strip into fixed-size transparent frames.""" + +from __future__ import annotations + +import argparse +from pathlib import Path +from typing import Iterable + +try: + from PIL import Image +except ImportError as exc: # pragma: no cover + raise SystemExit( + "Pillow is required. Install it with `python3 -m pip install pillow`." + ) from exc + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Extract one horizontal strip into fixed-size frames using a shared " + "global scale and bottom-center alignment." + ) + ) + parser.add_argument("--input", required=True, help="Path to the raw strip image.") + parser.add_argument("--out-dir", required=True, help="Output directory for frames.") + parser.add_argument( + "--frames", + type=int, + required=True, + help="Number of horizontal frames in the strip.", + ) + parser.add_argument( + "--frame-size", + type=int, + default=64, + help="Output square frame size in pixels. Default: 64.", + ) + parser.add_argument( + "--anchor", + help="Optional anchor frame used to stabilize global scale and frame 01 output.", + ) + parser.add_argument( + "--lock-frame1", + action="store_true", + help="Replace frame 01 with the provided anchor frame after normalization.", + ) + parser.add_argument( + "--alpha-threshold", + type=int, + default=8, + help="Pixels with alpha above this threshold count as sprite content. Default: 8.", + ) + return parser.parse_args() + + +def threshold_bbox(image: Image.Image, alpha_threshold: int) -> tuple[int, int, int, int] | None: + alpha = image.getchannel("A").point(lambda value: 255 if value > alpha_threshold else 0) + return alpha.getbbox() + + +def crop_to_content(image: Image.Image, alpha_threshold: int) -> Image.Image | None: + bbox = threshold_bbox(image, alpha_threshold) + if bbox is None: + return None + return image.crop(bbox) + + +def split_strip(strip: Image.Image, frames: int) -> list[Image.Image]: + if frames < 1: + raise ValueError("frames must be at least 1") + step = strip.width / frames + slots: list[Image.Image] = [] + for index in range(frames): + left = int(round(index * step)) + right = int(round((index + 1) * step)) + slots.append(strip.crop((left, 0, right, strip.height))) + return slots + + +def max_content_size(images: Iterable[Image.Image | None]) -> tuple[int, int]: + widths: list[int] = [] + heights: list[int] = [] + for image in images: + if image is None: + continue + widths.append(image.width) + heights.append(image.height) + if not widths or not heights: + raise SystemExit("No sprite content was detected in the provided strip.") + return max(widths), max(heights) + + +def compose_frame( + image: Image.Image | None, + frame_size: int, + scale: float, +) -> Image.Image: + canvas = Image.new("RGBA", (frame_size, frame_size), (0, 0, 0, 0)) + if image is None: + return canvas + + width = max(1, int(round(image.width * scale))) + height = max(1, int(round(image.height * scale))) + resized = image.resize((width, height), Image.Resampling.NEAREST) + offset_x = (frame_size - width) // 2 + offset_y = frame_size - height + canvas.alpha_composite(resized, (offset_x, offset_y)) + return canvas + + +def load_anchor(path: str | None, alpha_threshold: int) -> tuple[Image.Image | None, Image.Image | None]: + if path is None: + return None, None + anchor = Image.open(path).convert("RGBA") + cropped = crop_to_content(anchor, alpha_threshold) + return anchor, cropped + + +def main() -> None: + args = parse_args() + if args.frames < 1: + raise SystemExit("--frames must be at least 1.") + if args.frame_size < 1: + raise SystemExit("--frame-size must be positive.") + if args.lock_frame1 and not args.anchor: + raise SystemExit("--lock-frame1 requires --anchor.") + + strip = Image.open(args.input).convert("RGBA") + slots = split_strip(strip, args.frames) + contents = [crop_to_content(slot, args.alpha_threshold) for slot in slots] + anchor_image, anchor_content = load_anchor(args.anchor, args.alpha_threshold) + max_width, max_height = max_content_size([*contents, anchor_content]) + scale = min(args.frame_size / max_width, args.frame_size / max_height) + + out_dir = Path(args.out_dir) + out_dir.mkdir(parents=True, exist_ok=True) + + for index, content in enumerate(contents, start=1): + if index == 1 and args.lock_frame1: + assert anchor_image is not None + if anchor_image.width == args.frame_size and anchor_image.height == args.frame_size: + frame = anchor_image + else: + frame = compose_frame(anchor_content, args.frame_size, scale) + else: + frame = compose_frame(content, args.frame_size, scale) + frame.save(out_dir / f"{index:02d}.png") + + +if __name__ == "__main__": + main() diff --git a/.hermes/plugins/game-studio/scripts/render_sprite_preview_sheet.py b/.hermes/plugins/game-studio/scripts/render_sprite_preview_sheet.py new file mode 100644 index 00000000..8937ed83 --- /dev/null +++ b/.hermes/plugins/game-studio/scripts/render_sprite_preview_sheet.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +"""Render a simple contact sheet from a directory of normalized sprite frames.""" + +from __future__ import annotations + +import argparse +import math +import re +from pathlib import Path + +try: + from PIL import Image, ImageDraw +except ImportError as exc: # pragma: no cover + raise SystemExit( + "Pillow is required. Install it with `python3 -m pip install pillow`." + ) from exc + + +NUMBER_RE = re.compile(r"(\d+)") + + +def natural_key(path: Path) -> list[int | str]: + parts: list[int | str] = [] + for chunk in NUMBER_RE.split(path.stem): + if not chunk: + continue + if chunk.isdigit(): + parts.append(int(chunk)) + else: + parts.append(chunk) + parts.append(path.suffix) + return parts + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Render a preview contact sheet from a directory of sprite frames." + ) + parser.add_argument("--frames-dir", required=True, help="Directory containing PNG frames.") + parser.add_argument("--out", required=True, help="Output PNG path.") + parser.add_argument( + "--columns", + type=int, + default=4, + help="Number of columns in the preview sheet. Default: 4.", + ) + parser.add_argument( + "--gap", + type=int, + default=8, + help="Gap between frames in pixels. Default: 8.", + ) + return parser.parse_args() + + +def paint_checkerboard(image: Image.Image, tile: int = 16) -> None: + draw = ImageDraw.Draw(image) + colors = ((240, 243, 246, 255), (225, 230, 235, 255)) + for top in range(0, image.height, tile): + for left in range(0, image.width, tile): + color = colors[((left // tile) + (top // tile)) % 2] + draw.rectangle((left, top, left + tile, top + tile), fill=color) + + +def main() -> None: + args = parse_args() + if args.columns < 1: + raise SystemExit("--columns must be at least 1.") + if args.gap < 0: + raise SystemExit("--gap cannot be negative.") + + frame_dir = Path(args.frames_dir) + frames = sorted(frame_dir.glob("*.png"), key=natural_key) + if not frames: + raise SystemExit("No PNG frames were found in --frames-dir.") + + images = [Image.open(path).convert("RGBA") for path in frames] + frame_width = max(image.width for image in images) + frame_height = max(image.height for image in images) + rows = math.ceil(len(images) / args.columns) + sheet_width = args.columns * frame_width + max(0, args.columns - 1) * args.gap + sheet_height = rows * frame_height + max(0, rows - 1) * args.gap + sheet = Image.new("RGBA", (sheet_width, sheet_height), (255, 255, 255, 255)) + paint_checkerboard(sheet) + + for index, image in enumerate(images): + row = index // args.columns + column = index % args.columns + left = column * (frame_width + args.gap) + (frame_width - image.width) // 2 + top = row * (frame_height + args.gap) + (frame_height - image.height) // 2 + sheet.alpha_composite(image, (left, top)) + + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + sheet.save(out_path) + + +if __name__ == "__main__": + main() diff --git a/.hermes/plugins/game-studio/skills/game-playtest/SKILL.md b/.hermes/plugins/game-studio/skills/game-playtest/SKILL.md new file mode 100644 index 00000000..d74823c9 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/game-playtest/SKILL.md @@ -0,0 +1,76 @@ +--- +name: game-playtest +description: Run browser-game playtests and frontend QA. Use when the user asks for smoke tests, screenshot-based verification, browser automation, HUD or overlay review, or structured issue-finding in a browser game. +--- + +# Game Playtest + +## Overview + +Use this skill to test browser games the way players experience them: through boot, input, scene transitions, HUD readability, and visual state changes. Prefer browser automation and screenshot review when the project supports it. + +## Preferred Workflow + +1. Boot the game and confirm the first actionable screen. +2. Exercise the main verbs. +3. Capture screenshots from representative states. +4. Check the UI layer independently from the render layer. +5. Report findings in severity order with reproduction steps. + +## Tooling Guidance + +- Prefer Playwright or equivalent browser automation already available in the repo. +- When the game is canvas or WebGL heavy, screenshots are mandatory because DOM assertions alone miss visual regressions. +- Use screenshots to judge playfield obstruction and HUD weight, not just correctness of text or layout. +- When deterministic automation is not practical, do a structured manual pass and capture evidence. +- For 3D rendering bugs or unexplained frame cost, use SpectorJS and browser performance tooling rather than guessing from code alone. + +## Common Checks + +### 2D checks + +- sprite alignment and baseline consistency +- hit or hurt animation readability +- HUD overlap with the playfield +- command menu state changes +- tile or platform readability +- input-state feedback and turn-state clarity + +### 3D checks + +- first-load playability versus dashboard-like chrome +- persistent overlay weight versus playfield visibility +- camera control and camera reset behavior +- pointer-lock or drag-look transitions when menus and overlays open +- depth readability and silhouette clarity +- secondary panels collapsed or dismissible during normal play +- resize behavior +- WebGL context loss or renderer fallback behavior +- material or lighting regressions +- GLB or texture streaming stalls +- collision proxy or physics mismatch +- performance cliffs tied to post-processing or asset load + +## Responsive and Browser Checks + +- desktop and mobile viewport sanity +- safe-area and notch issues where relevant +- reduced-motion behavior for UI transitions +- keyboard, pointer, and pause-state handling +- React state and scene state synchronization when the project uses React Three Fiber + +## Reporting Standard + +Lead with findings. Keep each finding concrete: + +- what the user sees +- how to reproduce it +- why it matters +- what likely subsystem owns it + +## References + +- Shared architecture: `../web-game-foundations/SKILL.md` +- Frontend review cues: `../game-ui-frontend/SKILL.md` +- 3D debugging notes: `../../references/webgl-debugging-and-performance.md` +- Full checklist: `../../references/playtest-checklist.md` diff --git a/.hermes/plugins/game-studio/skills/game-playtest/agents/openai.yaml b/.hermes/plugins/game-studio/skills/game-playtest/agents/openai.yaml new file mode 100644 index 00000000..9ef3afd8 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/game-playtest/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Game Playtest" + short_description: "Run browser-game playtests and QA" + default_prompt: "Playtest the browser game, check core interactions and visual state changes, and report concrete issues." diff --git a/.hermes/plugins/game-studio/skills/game-studio/SKILL.md b/.hermes/plugins/game-studio/skills/game-studio/SKILL.md new file mode 100644 index 00000000..ba409990 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/game-studio/SKILL.md @@ -0,0 +1,94 @@ +--- +name: game-studio +description: Route early browser-game work. Use when the user needs stack selection and workflow planning across design, implementation, assets, and playtesting before moving to a specialist skill. +--- + +# Game Studio + +## Overview + +Use this skill as the umbrella entrypoint for browser-game work. Default to a 2D Phaser path unless the user explicitly asks for 3D, Three.js, React Three Fiber, shader-heavy rendering, or another WebGL-first direction. + +This plugin is intentionally asymmetric: + +- 2D is the strongest execution path in v1. +- 3D has one opinionated default ecosystem: vanilla Three.js for plain TypeScript or Vite apps, React Three Fiber for React-hosted 3D apps, and GLB or glTF 2.0 as the default shipping asset format. +- Shared architecture, UI, and playtest practices apply to both. + +## Use This Skill When + +- the user is still choosing a stack +- the request spans multiple domains such as runtime, UI, asset pipeline, and QA +- the user says "help me build a game" without naming the implementation path + +## Do Not Stay Here When + +- the runtime is clearly plain Three.js +- the runtime is clearly React Three Fiber +- the task is clearly a shipped-asset problem +- the task is clearly frontend-only or QA-only + +Once the intent is clear, route to the most specific specialist skill and continue from there. + +## Routing Rules + +1. Classify the request before designing or coding: + - `2D default`: Phaser, sprites, tilemaps, top-down, side-view, grid tactics, action platformers. + - `3D + plain TS/Vite`: imperative scene control, engine-like loops, non-React apps, direct Three.js work. + - `3D + React`: React-hosted product surfaces, declarative scene composition, shared React state, UI-heavy 3D apps. + - `3D asset pipeline`: GLB, glTF, texture packaging, compression, LOD, runtime asset size. + - `Alternative engine`: Babylon.js or PlayCanvas requests, usually as comparison or ecosystem fit questions. + - `Shared`: core loop design, frontend direction, save/debug/perf boundaries, browser QA. +2. Route to the specialist skills immediately after classification: + - Shared architecture and engine choice: `../web-game-foundations/SKILL.md` + - Deep 2D implementation: `../phaser-2d-game/SKILL.md` + - Vanilla Three.js implementation: `../three-webgl-game/SKILL.md` + - React-hosted 3D implementation: `../react-three-fiber-game/SKILL.md` + - 3D asset shipping and optimization: `../web-3d-asset-pipeline/SKILL.md` + - HUD and menu direction: `../game-ui-frontend/SKILL.md` + - 2D sprite generation and normalization: `../sprite-pipeline/SKILL.md` + - Browser QA and visual review: `../game-playtest/SKILL.md` +3. Keep one coherent plan across the routed skills. Do not let engine, UI, asset, and QA decisions drift apart. + +## Default Workflow + +1. Lock the game fantasy and player verbs. +2. Define the core loop, failure states, progression, and target play session length. +3. Choose the implementation track: + - Default to Phaser for 2D browser games. + - Choose vanilla Three.js when the project is explicitly 3D and wants direct render-loop control in a plain TypeScript or Vite app. + - Choose React Three Fiber when the project already lives in React or wants declarative scene composition with shared React state. + - Choose raw WebGL only when the user explicitly wants a custom renderer or shader-first surface. +4. Define the UI surface early. Browser games usually need a DOM HUD and menu layer even when the playfield is canvas or WebGL. + - For 3D starter scaffolds, default to a low-chrome HUD that preserves the playfield and keeps secondary panels collapsed. +5. Decide the asset workflow: + - 2D characters and effects: use `sprite-pipeline`. + - 3D models, textures, and shipping format: use `web-3d-asset-pipeline`. +6. Close with a playtest loop before calling the work production-ready. + +## Output Expectations + +- For planning requests, return a game-specific plan with stack choice, gameplay loop, UI surface, asset workflow, and test approach. +- For implementation requests, keep the chosen stack obvious in the file structure and code boundaries. +- For mixed requests, preserve the plugin default: 2D Phaser first unless the user asks for something else. +- When the user asks about Babylon.js or PlayCanvas, compare them honestly but keep Three.js and R3F as the primary code-generation defaults unless the user explicitly chooses another engine. + +## References + +- Engine selection: `../../references/engine-selection.md` +- Three.js stack: `../../references/threejs-stack.md` +- React Three Fiber stack: `../../references/react-three-fiber-stack.md` +- 3D asset pipeline: `../../references/web-3d-asset-pipeline.md` +- Vanilla Three.js starter: `../../references/threejs-vanilla-starter.md` +- React Three Fiber starter: `../../references/react-three-fiber-starter.md` +- Frontend prompting patterns: `../../references/frontend-prompts.md` +- Playtest checklist: `../../references/playtest-checklist.md` + +## Examples + +- "Help me prototype a browser tactics game." +- "I need a Phaser-based action game loop with a HUD and menus." +- "I want a Three.js exploration demo with WebGL lighting and browser-safe UI." +- "I want a React-based 3D configurator with React Three Fiber." +- "Optimize my GLB assets for the web and keep the file sizes under control." +- "Set up the asset workflow for consistent 2D sprite animations." diff --git a/.hermes/plugins/game-studio/skills/game-studio/agents/openai.yaml b/.hermes/plugins/game-studio/skills/game-studio/agents/openai.yaml new file mode 100644 index 00000000..2179b571 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/game-studio/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Game Studio" + short_description: "Route browser-game work to the right path" + default_prompt: "Help me choose the right browser-game stack and workflow before implementation starts." diff --git a/.hermes/plugins/game-studio/skills/game-ui-frontend/SKILL.md b/.hermes/plugins/game-studio/skills/game-ui-frontend/SKILL.md new file mode 100644 index 00000000..f2681fb9 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/game-ui-frontend/SKILL.md @@ -0,0 +1,112 @@ +--- +name: game-ui-frontend +description: Design UI surfaces for browser games. Use when the user asks for HUDs, menus, overlays, responsive layouts, or visual direction that must protect the playfield. +--- + +# Game UI Frontend + +## Overview + +Use this skill whenever the game needs a visible interface layer. The job is not to produce generic dashboard UI. The job is to produce a readable, thematic browser-game interface that supports the play experience. + +Default assumption: build the game world in canvas or WebGL, and build text-heavy UI in DOM. + +## Frontend Standards + +1. Establish visual direction before coding. + - Genre and fantasy + - Material language + - Typography + - Palette + - Motion tone +2. Use CSS variables for the UI theme. +3. Build clear hierarchy. + - Critical combat or survival information first + - Secondary tools second + - Rarely used settings behind menus or drawers +4. Protect the playfield first, especially in 3D. + - The initial screen should feel playable within a few seconds. + - Default to one primary persistent HUD cluster and at most one small secondary cluster. + - Keep the center of the playfield clear during normal play. + - Keep the lower-middle playfield mostly clear during normal play. + - Put lore, field notes, quest details, and long control lists behind drawers, toggles, or pause surfaces. + - Prefer contextual prompts and transient hints over permanent boxed panels. +5. Keep overlays readable over motion. + - Use backing panels, edge treatment, contrast, and restrained blur where needed. +6. Design for both desktop and mobile from the start. +7. Design 3D UI around camera and input control boundaries. + - Pause or gate camera-control input when menus, dialogs, or pointer-driven UI are active. + - Keep pointer-lock, drag-to-look, and menu interaction states explicit. + +## 3D Starter Defaults + +For exploration, traversal, or third-person starter scaffolds, prefer this UI budget: + +- one compact objective chip or status strip at the edge +- one transient controls hint or interaction prompt +- one optional collapsible secondary surface such as a journal, map, or quest log + +Do not open every informational surface on first load. The scene should be readable before the user opens any deeper UI. + +As a default implementation constraint for 3D browser games: + +- no always-on full-width header plus multi-card body plus full-width footer layout +- no large center-screen or lower-middle overlays during normal movement +- no more than roughly 20-25% of the viewport covered by persistent HUD on desktop unless the user explicitly requests a denser layout +- on mobile, collapse to a narrow stack or contextual chips before covering the playfield with larger panels + +## Prompting Rules + +When asking the model to design or implement game UI, include: + +- the game fantasy +- the camera or viewpoint +- the player verbs +- the HUD layers +- the camera or control mode when the game is 3D +- the tone of motion +- desktop and mobile expectations +- playfield protection and disclosure strategy +- explicit anti-patterns to avoid + +Use `../../references/frontend-prompts.md` for concrete prompt shapes. + +## Motion Rules + +- Prefer a few meaningful transitions over constant micro-animation. +- Reserve strong motion for state change, reward, danger, and onboarding. +- Respect reduced-motion settings for non-essential animation. +- Keep 3D HUD motion from competing with camera motion. + +## What Good Looks Like + +- HUD elements are legible without flattening the scene. +- Menus feel native to the game world, not like a SaaS admin panel. +- Layout adapts cleanly across breakpoints. +- Pointer, keyboard, and game-state feedback are obvious. +- In 3D games, menu and HUD states do not fight camera control or pointer-lock. +- In 3D games, the first playable view keeps most of the viewport available for movement, aiming, and spatial reading. +- Persistent information density is low enough that screenshots still read as game scenes, not UI comps. + +## Anti-Patterns + +- Generic app dashboard layouts +- Flat placeholder styling with no theme +- Default font stacks without intent +- Dense overlays that obscure the playfield +- Large title cards or multi-paragraph notes sitting over a live playable scene +- Equal-weight boxed panels distributed around every edge of the viewport +- Controls, objectives, notes, and lore all expanded at once on first load +- Full-width top-and-bottom chrome with large always-on center or body panels in 3D play +- Excessive motion on every element +- Canvas-only UI when DOM would be clearer and cheaper +- Forcing HUD controls into the 3D scene when standard DOM would be clearer +- Letting camera input remain active under modals or inventory panels + +## References + +- Shared architecture: `../web-game-foundations/SKILL.md` +- Prompt recipes: `../../references/frontend-prompts.md` +- Low-chrome 3D layout patterns: `../../references/three-hud-layout-patterns.md` +- React-hosted 3D UI context: `../react-three-fiber-game/SKILL.md` +- Playtest review: `../../references/playtest-checklist.md` diff --git a/.hermes/plugins/game-studio/skills/game-ui-frontend/agents/openai.yaml b/.hermes/plugins/game-studio/skills/game-ui-frontend/agents/openai.yaml new file mode 100644 index 00000000..38d5e21d --- /dev/null +++ b/.hermes/plugins/game-studio/skills/game-ui-frontend/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Game UI Frontend" + short_description: "Design browser-game HUDs, menus, and overlays" + default_prompt: "Design a browser-game UI layer that supports the play experience without crowding the playfield." diff --git a/.hermes/plugins/game-studio/skills/phaser-2d-game/SKILL.md b/.hermes/plugins/game-studio/skills/phaser-2d-game/SKILL.md new file mode 100644 index 00000000..8bd4ed6a --- /dev/null +++ b/.hermes/plugins/game-studio/skills/phaser-2d-game/SKILL.md @@ -0,0 +1,88 @@ +--- +name: phaser-2d-game +description: Implement 2D browser games with Phaser. Use when the user wants a Phaser, TypeScript, and Vite stack for scenes, gameplay systems, cameras, sprite animation, and DOM-overlay HUD patterns. +--- + +# Phaser 2D Game + +## Overview + +Use this skill for the main execution path in this plugin. Phaser is the default stack for 2D browser games here because it handles rendering, timing, sprites, cameras, and scene orchestration well without forcing gameplay rules into the framework. + +Preferred stack: + +- Phaser +- TypeScript +- Vite +- DOM-based HUD or menus layered over the game canvas + +## Architecture + +1. Keep gameplay state outside Phaser scenes. + - Systems own rules, turn order, movement, combat, inventory, objectives, and progression. + - Phaser scenes adapt system state into sprites, camera motion, animation playback, and effects. +2. Make scenes thin. + - Boot and asset preload + - Menu or shell scene + - Gameplay scene + - Optional overlay or debug scene +3. Keep renderer-facing objects disposable. + - Sprite containers, emitters, tweens, and camera rigs are view state, not source of truth. +4. Favor stable asset manifest keys over direct file-path references throughout gameplay code. + +## Implementation Guidance + +- Use one integration boundary where the scene reads simulation state and emits input actions back. +- Prefer deterministic system updates over scene-local mutation. +- Treat HUD and menus as DOM when text, status density, or responsiveness matter. +- Keep animation state derived from gameplay state rather than ad hoc sprite flags. + +## 2D Modes Covered Well + +- Turn-based grids and tactics +- Top-down exploration +- Side-view action platformers +- Character-action combat with sprite animation +- Lightweight management or deck-driven battle scenes + +## Camera and Presentation + +- Choose the camera model early: locked, follow, room-based, or tactical-pan. +- Keep camera logic separate from game rules. +- Use restrained screen shake, hit-stop, and parallax. Effects should improve readability, not obscure it. + +## UI Integration + +- Use DOM overlays for HUD, command menus, settings, and narrative panels. +- Keep the canvas responsible for the world, combat readability, and motion. +- Avoid shoving dense text or complex settings UIs into Phaser unless the project explicitly needs an in-canvas presentation. + +## Asset Organization + +- `characters/` +- `environment/` +- `ui/` +- `fx/` +- `audio/` +- `data/` + +Keep manifest keys human-readable and stable. + +## Default Directory Shape + +See `../../references/phaser-architecture.md` for a concrete module split. + +## Anti-Patterns + +- Game rules inside `update()` loops without a system boundary +- Scene-to-scene state passed through mutable global objects +- HUD text rendered in the game canvas just because it is convenient +- Asset paths embedded everywhere instead of a manifest layer +- Overusing generic React dashboard patterns for game UI + +## References + +- Shared architecture: `../web-game-foundations/SKILL.md` +- Frontend direction: `../game-ui-frontend/SKILL.md` +- Sprite workflow: `../sprite-pipeline/SKILL.md` +- Phaser structure: `../../references/phaser-architecture.md` diff --git a/.hermes/plugins/game-studio/skills/phaser-2d-game/agents/openai.yaml b/.hermes/plugins/game-studio/skills/phaser-2d-game/agents/openai.yaml new file mode 100644 index 00000000..c715eb75 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/phaser-2d-game/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Phaser 2D Game" + short_description: "Build 2D browser games with Phaser" + default_prompt: "Implement this 2D browser game with Phaser, TypeScript, and a clear gameplay architecture." diff --git a/.hermes/plugins/game-studio/skills/react-three-fiber-game/SKILL.md b/.hermes/plugins/game-studio/skills/react-three-fiber-game/SKILL.md new file mode 100644 index 00000000..90fb0657 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/react-three-fiber-game/SKILL.md @@ -0,0 +1,87 @@ +--- +name: react-three-fiber-game +description: Build React-hosted 3D browser games with React Three Fiber. Use when the user wants pmndrs-based scene composition, shared React state, and 3D HUD integration inside a React app. +--- + +# React Three Fiber Game + +## Overview + +Use this skill when the 3D runtime lives inside a React application. This is the default React-native 3D path in the plugin and should be preferred over vanilla Three.js when the app shell, settings, storefront, editor surface, or surrounding product already uses React. + +Recommended stack: + +- `@react-three/fiber` +- `three` +- `@react-three/drei` +- `@react-three/rapier` +- `@react-three/postprocessing` +- `@react-three/a11y` when accessibility-sensitive interaction matters +- DOM overlays in the normal React tree + +## Use This Skill When + +- the project already uses React +- the 3D scene must share state with the rest of the app +- declarative scene composition is a net gain +- the team wants pmndrs helpers instead of building every helper layer by hand + +## Do Not Use This Skill When + +- the app is not React-based +- the project wants a cleaner imperative runtime with minimal React coordination +- the problem is asset packaging rather than runtime composition + +## Best Fit Scenarios + +- 3D configurators and tool-rich browser products +- React apps with embedded game or scene surfaces +- 3D menus, editors, or world maps in an existing React app +- 3D game UIs that depend on shared app state and non-canvas shells + +## Core Rules + +1. Keep simulation state outside render components. + - React components should describe scene composition, not become the source of truth for gameplay rules. +2. Use React state and scene state deliberately. + - Shared UI state can live in app state. + - High-frequency simulation should not force the whole app through unnecessary React churn. +3. Use pmndrs helpers intentionally. + - Drei for controls, loaders, helpers, environments, and common scene primitives. + - `@react-three/rapier` for physics integration. + - `@react-three/postprocessing` for optional effects. + - `@react-three/a11y` when the interaction model benefits from accessible scene semantics. +4. Keep HUD, settings, and menus in DOM by default. +5. Keep starter scaffolds visually restrained. + - Start with one compact objective or status surface and transient prompts. + - Keep notes, maps, and multi-step checklists collapsed until opened. + - Do not surround the `Canvas` with equally weighted glass cards. + +## Architectural Guidance + +- Use a dedicated scene root component that owns the `Canvas`. +- Keep camera rigs and control components isolated from gameplay systems. +- Keep loader and asset wrappers predictable. +- Keep DOM overlays and the 3D scene coordinated through explicit state boundaries. +- If a system needs tight imperative control, isolate it rather than forcing everything into declarative patterns. +- If the scene is immediately playable, keep the initial overlay budget low and let the world do more of the onboarding. + +## Anti-Patterns + +- Treating React components as the gameplay state store +- Pushing heavy per-frame mutation through broad app state +- Using R3F only because React is available, even when the project needs a cleaner imperative runtime +- Building HUD or inventory UI inside the 3D scene by default +- Shipping an initial scaffold with large cards occupying every side of the viewport + +## References + +- Shared architecture: `../web-game-foundations/SKILL.md` +- Frontend direction: `../game-ui-frontend/SKILL.md` +- 3D HUD layout patterns: `../../references/three-hud-layout-patterns.md` +- React Three Fiber stack: `../../references/react-three-fiber-stack.md` +- React starter: `../../references/react-three-fiber-starter.md` +- GLB loader starter: `../../references/gltf-loading-starter.md` +- Rapier starter: `../../references/rapier-integration-starter.md` +- 3D asset pipeline: `../../references/web-3d-asset-pipeline.md` +- WebGL debugging and perf: `../../references/webgl-debugging-and-performance.md` diff --git a/.hermes/plugins/game-studio/skills/react-three-fiber-game/agents/openai.yaml b/.hermes/plugins/game-studio/skills/react-three-fiber-game/agents/openai.yaml new file mode 100644 index 00000000..6bb67f35 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/react-three-fiber-game/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "React Three Fiber Game" + short_description: "Build React-hosted 3D browser games" + default_prompt: "Build this 3D browser game with React Three Fiber and keep the 3D runtime aligned with the React app shell." diff --git a/.hermes/plugins/game-studio/skills/sprite-pipeline/SKILL.md b/.hermes/plugins/game-studio/skills/sprite-pipeline/SKILL.md new file mode 100644 index 00000000..a31ef15c --- /dev/null +++ b/.hermes/plugins/game-studio/skills/sprite-pipeline/SKILL.md @@ -0,0 +1,102 @@ +--- +name: sprite-pipeline +description: Generate and normalize 2D sprite animations. Use when the user asks for full-strip generation from approved source frames, consistent anchor and scale normalization, or preview assets for browser-game animation. +--- + +# Sprite Pipeline + +## Overview + +Use this skill for 2D sprite generation and normalization. This workflow is intentionally anchored around one approved frame and a whole-strip generation pass because frame-by-frame generation drifts too easily. + +This skill is 2D-specific. If the request is for 3D characters, meshes, or materials, route back through `../game-studio/SKILL.md`. + +## Core Workflow + +1. Start from an approved in-game seed frame. + - The seed frame should already reflect the right silhouette, palette, costume, and proportions. +2. Build a larger transparent reference canvas around that frame. + - Use `../../scripts/build_sprite_edit_canvas.py`. +3. Ask for the full animation strip in one edit request. + - Do not generate each frame independently unless the user explicitly accepts lower consistency. +4. Normalize the result into fixed-size game frames. + - Use `../../scripts/normalize_sprite_strip.py`. + - Use one shared scale across the whole strip. + - Align frames with one shared anchor, typically bottom-center. +5. Optionally lock frame 01 back to the shipped seed frame. + - Do this when the animation should begin from the exact idle or base pose already in game. +6. Render a preview sheet and inspect the animation in-engine before approving it. + - Use `../../scripts/render_sprite_preview_sheet.py`. + +## Prompting Rules + +Always preserve these invariants in the prompt: + +- same character +- same facing direction +- same palette family +- same silhouette family +- same readable face or key features +- same outfit proportions +- transparent background +- exact frame count and slot layout + +Always ask for: + +- one strip at once +- a transparent canvas +- no scenery, labels, or poster composition +- crisp pixel-art clusters for pixel work +- production asset tone, not concept art + +## Using Image Generation + +For live asset generation or edits, use the installed `imagegen` skill in this workspace. This skill defines the game-specific process; `imagegen` handles the API-backed generation or edit execution. + +## Script Recipes + +Create a reference canvas: + +```bash +python3 scripts/build_sprite_edit_canvas.py \ + --seed output/sprites/idle-01.png \ + --out output/sprites/hurt-edit-canvas.png \ + --frames 4 \ + --slot-size 256 \ + --canvas-size 1024 +``` + +Normalize a raw strip: + +```bash +python3 scripts/normalize_sprite_strip.py \ + --input output/sprites/hurt-raw.png \ + --out-dir output/sprites/hurt \ + --frames 4 \ + --frame-size 64 \ + --anchor output/sprites/idle-01.png \ + --lock-frame1 +``` + +Render a preview sheet: + +```bash +python3 scripts/render_sprite_preview_sheet.py \ + --frames-dir output/sprites/hurt \ + --out output/sprites/hurt-preview.png \ + --columns 4 +``` + +## Quality Gates + +- proportions stay stable across frames +- frame-to-frame size does not drift +- action reads clearly at game scale +- transparency is preserved +- frame 01 matches the shipped sprite when lockback is enabled +- preview looks correct before any in-engine asset index update + +## References + +- Detailed workflow: `../../references/sprite-pipeline.md` +- Shared frontend context: `../game-ui-frontend/SKILL.md` diff --git a/.hermes/plugins/game-studio/skills/sprite-pipeline/agents/openai.yaml b/.hermes/plugins/game-studio/skills/sprite-pipeline/agents/openai.yaml new file mode 100644 index 00000000..fe416922 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/sprite-pipeline/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sprite Pipeline" + short_description: "Generate and normalize 2D sprite animations" + default_prompt: "Create and normalize 2D sprite animation assets for a browser game with consistent scale and anchors." diff --git a/.hermes/plugins/game-studio/skills/three-webgl-game/SKILL.md b/.hermes/plugins/game-studio/skills/three-webgl-game/SKILL.md new file mode 100644 index 00000000..647cbcad --- /dev/null +++ b/.hermes/plugins/game-studio/skills/three-webgl-game/SKILL.md @@ -0,0 +1,124 @@ +--- +name: three-webgl-game +description: Implement browser-game runtimes with plain Three.js. Use when the user wants imperative scene control in TypeScript or Vite with GLB assets, loaders, physics, and low-level WebGL debugging. +--- + +# Three WebGL Game + +## Overview + +Use this skill for the default non-React 3D path in the plugin. This is not generic WebGL advice. It is an opinionated stack for browser 3D work: + +- `three` +- TypeScript +- Vite +- GLB or glTF 2.0 assets +- Three.js loaders such as `GLTFLoader`, `DRACOLoader`, and `KTX2Loader` +- Rapier JS for physics +- SpectorJS for GPU and frame debugging +- DOM overlays for HUD, menus, and settings + +Use this skill when the project wants direct scene, camera, renderer, and game-loop control. If the app already lives in React, route to `../react-three-fiber-game/SKILL.md` instead. + +## Use This Skill When + +- the app is plain TypeScript or Vite rather than React-first +- the project wants direct imperative control over the render loop +- the user asks for Three.js specifically +- the runtime needs engine-like control over scene, camera, loaders, and physics + +## Do Not Use This Skill When + +- the 3D scene lives inside an existing React app +- the main problem is shipped-asset optimization rather than runtime code +- the user explicitly chose Babylon.js or PlayCanvas + +## Core Rules + +1. Keep simulation state outside Three.js objects. + - Game rules, AI, quest state, timers, and progression should not live inside meshes or materials. +2. Treat the render graph as an adapter. + - Scene graph, cameras, materials, loaders, and post-processing are view concerns layered over simulation state. +3. Keep camera behavior explicit. + - Orbit, follow, chase, rail, and first-person styles each need their own control boundary. +4. Keep UI out of WebGL unless the presentation absolutely depends on it. + - Menus, HUD, inventories, and settings should default to DOM. +5. Use GLB or glTF 2.0 as the default shipping model format. + - Do not build the runtime around DCC-native formats. +6. Use Rapier instead of ad hoc collision code when the game has meaningful 3D physics or collision response. +7. Keep the first playable view low-chrome. + - Default to one compact objective or status cluster plus transient prompts. + - Long notes, lore, and controls references should be collapsed until asked for. + - Do not frame the scene with multiple equal-weight cards during normal play. + +## Initial Scaffold UX + +For exploration, traversal, and character-control prototypes, start with a sparse shell: + +- one edge-aligned objective chip +- one transient controls hint +- one optional compact status strip + +Only add larger UI surfaces when the game loop truly requires them. Journal, quest log, codex, map, and settings surfaces should open on demand, not occupy the viewport by default. + +## Recommended Structure + +Use the module shape in `../../references/three-webgl-architecture.md`, then keep these boundaries clean: + +- `simulation/`: rules, progression, state, and AI +- `render/app/`: renderer, scene, camera, resize, context lifecycle +- `render/loaders/`: GLTF, Draco, KTX2, texture, and environment loading +- `render/objects/`: mesh instantiation and disposal +- `render/materials/`: material setup and shader boundaries +- `physics/`: Rapier world, bodies, colliders, and simulation bridge +- `ui/`: DOM overlays and menus +- `diagnostics/`: debug toggles, perf probes, and capture hooks + +## Good Fit Scenarios + +- Exploration demos +- Lightweight 3D combat prototypes +- Vehicle or traversal prototypes +- Scene-driven product or world showcases with gameplay +- Material, lighting, or post-process-led experiences +- 3D games where camera movement and depth readability are central + +## Loaders, Assets, and Post-Processing + +- Start with `GLTFLoader` for shipped 3D content. +- Add `DRACOLoader` or Meshopt-compatible optimization as part of the asset pipeline, not as a random runtime patch. +- Use `KTX2Loader` for compressed textures when the asset pipeline provides them. +- Prefer built-in Three.js render and post-processing utilities first. Add heavier abstraction only when the project actually needs it. +- Keep post-processing optional and measurable. Bloom and color effects should not hide gameplay readability. + +## Shader and Material Guidance + +- Start with standard Three.js materials and correct lighting before reaching for custom shaders. +- Use custom shaders only when the visual target genuinely needs them. +- Keep shader parameters driven by game state, not by incidental scene mutations. +- If a material stack gets complex, isolate it behind material factories instead of scattering shader setup across scene code. + +## Browser Safety + +- Handle resize explicitly. +- Expect WebGL context loss and recovery. +- Keep a fallback or degraded mode in mind for fragile GPU paths. +- Watch texture size, geometry count, draw-call growth, and post-processing cost. +- Use SpectorJS when the scene behaves incorrectly or frame cost is unclear. + +## Scope Warning + +Do not claim that this plugin offers equal 3D depth to the Phaser track. It supports serious 3D implementation, but the plugin is still 2D-first overall. + +## References + +- Shared architecture: `../web-game-foundations/SKILL.md` +- Frontend direction: `../game-ui-frontend/SKILL.md` +- 3D HUD layout patterns: `../../references/three-hud-layout-patterns.md` +- Three.js ecosystem: `../../references/threejs-stack.md` +- Three.js structure: `../../references/three-webgl-architecture.md` +- Vanilla starter: `../../references/threejs-vanilla-starter.md` +- GLB loader starter: `../../references/gltf-loading-starter.md` +- Rapier starter: `../../references/rapier-integration-starter.md` +- 3D asset pipeline: `../../references/web-3d-asset-pipeline.md` +- WebGL debugging and perf: `../../references/webgl-debugging-and-performance.md` diff --git a/.hermes/plugins/game-studio/skills/three-webgl-game/agents/openai.yaml b/.hermes/plugins/game-studio/skills/three-webgl-game/agents/openai.yaml new file mode 100644 index 00000000..898f8c7c --- /dev/null +++ b/.hermes/plugins/game-studio/skills/three-webgl-game/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Three WebGL Game" + short_description: "Build browser-game runtimes with Three.js" + default_prompt: "Implement this browser-game runtime with plain Three.js and keep the scene architecture easy to debug." diff --git a/.hermes/plugins/game-studio/skills/web-3d-asset-pipeline/SKILL.md b/.hermes/plugins/game-studio/skills/web-3d-asset-pipeline/SKILL.md new file mode 100644 index 00000000..d0fde630 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/web-3d-asset-pipeline/SKILL.md @@ -0,0 +1,78 @@ +--- +name: web-3d-asset-pipeline +description: Prepare and optimize browser-game 3D assets. Use when the user asks for GLB or glTF shipping work, including Blender cleanup and export, collision or LOD setup, compression, texture packaging, and runtime validation. +--- + +# Web 3D Asset Pipeline + +## Overview + +Use this skill for shipped 3D assets, not runtime scene code. The default output format for browser 3D work in this plugin is GLB or glTF 2.0. The goal is predictable runtime assets, not whatever the DCC tool happened to export first. + +This guidance is engine-agnostic and can serve Three.js, React Three Fiber, Babylon.js, or PlayCanvas. + +## Use This Skill When + +- the task is about GLB or glTF shipping format +- the task is about model cleanup, texture packaging, compression, LOD, or collision proxies +- the runtime stack is already chosen and the remaining problem is asset quality or size + +## Do Not Use This Skill When + +- the task is about scene, camera, renderer, or game-loop structure +- the task is purely about React versus vanilla Three.js routing +- the user is still deciding between runtime engines + +## Default Pipeline + +1. Author and clean the source asset in a DCC tool such as Blender. +2. Export to GLB or glTF 2.0. +3. Optimize with glTF Transform. +4. Validate naming, pivots, transforms, material reuse, and texture budgets. +5. Add collision proxies, LOD strategy, and baked-lighting assumptions as needed. +6. Ship the optimized asset and load it with engine-native GLTF support. + +## Format Rules + +- Default shipping format: GLB or glTF 2.0. +- Do not treat FBX, OBJ, or DCC-native formats as the long-term runtime contract. +- Apply or normalize transforms before shipping. +- Keep units, pivots, and orientation conventions consistent across the whole asset set. + +## Optimization Rules + +- Use glTF Transform for pruning, deduplication, simplification, and packaging. +- Use geometry compression intentionally. + - Draco is a valid option when decode cost and compatibility fit the runtime. + - Meshopt is often a strong default for web delivery. +- Compress textures deliberately. + - Use KTX2 or BasisU when the runtime stack supports it. + - Use WebP or AVIF where they make sense in the broader asset pipeline. +- Reuse materials and textures where possible to cut memory and draw-call cost. + +## Runtime-Ready Asset Rules + +- Keep model hierarchy names stable and meaningful. +- Set pivots and origins for gameplay interaction, not just for DCC convenience. +- Author explicit collision proxies for physics-heavy scenes. +- Decide whether lighting is dynamic, baked, or hybrid before final export. +- Plan LODs for large environments or repeated props. +- Keep texture resolution proportional to on-screen use, not source-art ambition. + +## Common Failure Modes + +- Shipping raw DCC exports without cleanup +- Too many unique materials +- Texture sizes far above visible need +- Missing collision proxies +- Scale or pivot mismatches between assets +- Runtime code compensating for asset mistakes that should be fixed upstream + +## References + +- Three.js stack: `../../references/threejs-stack.md` +- React Three Fiber stack: `../../references/react-three-fiber-stack.md` +- GLB loader starter: `../../references/gltf-loading-starter.md` +- Rapier starter: `../../references/rapier-integration-starter.md` +- 3D asset pipeline reference: `../../references/web-3d-asset-pipeline.md` +- Alternative engines: `../../references/alternative-3d-engines.md` diff --git a/.hermes/plugins/game-studio/skills/web-3d-asset-pipeline/agents/openai.yaml b/.hermes/plugins/game-studio/skills/web-3d-asset-pipeline/agents/openai.yaml new file mode 100644 index 00000000..308ca2b5 --- /dev/null +++ b/.hermes/plugins/game-studio/skills/web-3d-asset-pipeline/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Web 3D Asset Pipeline" + short_description: "Prepare and optimize browser-game 3D assets" + default_prompt: "Prepare these browser-game 3D assets for shipping as predictable runtime-ready GLB or glTF files." diff --git a/.hermes/plugins/game-studio/skills/web-game-foundations/SKILL.md b/.hermes/plugins/game-studio/skills/web-game-foundations/SKILL.md new file mode 100644 index 00000000..1fe88e9c --- /dev/null +++ b/.hermes/plugins/game-studio/skills/web-game-foundations/SKILL.md @@ -0,0 +1,95 @@ +--- +name: web-game-foundations +description: Set browser-game architecture before implementation. Use when the user needs engine choice, simulation and render boundaries, input model, asset organization, or save/debug/performance strategy. +--- + +# Web Game Foundations + +## Overview + +Use this skill to establish the non-negotiable architecture before implementation starts. Browser games degrade quickly when simulation, rendering, UI, asset loading, and input handling are mixed together. + +Default rule: simulation state is owned outside the renderer, browser UI is not forced into the canvas unless there is a clear reason, and shipped 3D assets default to GLB or glTF 2.0 rather than ad hoc model formats. + +## Use This Skill When + +- the user has not settled the engine or renderer choice +- the task is about boundaries, module shape, state ownership, or asset policy +- multiple specialist skills need one shared architectural frame + +## Do Not Stay Here When + +- the runtime track is clearly Phaser +- the runtime track is clearly vanilla Three.js +- the runtime track is clearly React Three Fiber +- the task is purely about shipped 3D assets + +Once the stack is clear, hand off to the runtime or asset specialist skill. + +## Architecture Rules + +1. Separate simulation from rendering. + - Simulation owns entities, turns, timers, collisions, progression, and saveable state. + - The renderer owns scene composition, animation playback, camera, particles, and input plumbing. +2. Keep input mapping explicit. + - Define actions such as `move`, `confirm`, `cancel`, `ability-1`, and `pause`. + - Map physical inputs to actions in one place. +3. Treat asset loading as a first-class system. + - Use stable manifest keys. + - Group by domain: characters, environment, UI, audio, FX. + - For 3D content, standardize on GLB or glTF 2.0 unless the chosen engine ecosystem requires another format upstream. +4. Define save/debug/perf boundaries up front. + - Save serializable simulation state, not renderer objects. + - Keep debug overlays and perf probes easy to toggle. +5. Use DOM overlays for menus and HUD by default. + - Canvas or WebGL should handle the playfield. + - DOM should handle text-heavy HUD, menus, settings, and accessibility-sensitive controls. + - In 3D, keep the persistent UI budget small so the scene stays readable and interactive. +6. Lock 3D runtime conventions early. + - Choose consistent units, origins, pivots, and naming conventions. + - Decide how collision proxies, LODs, and baked lighting data are authored before runtime integration starts. + +## Engine Selection + +- Default to Phaser for 2D games with sprites, tilemaps, top-down or side-view action, turn-based grids, and classic browser arcade flows. +- Default to vanilla Three.js for explicit 3D scenes that want direct scene, camera, renderer, and loop control in plain TypeScript or Vite. +- Default to React Three Fiber when the 3D scene lives inside a React application and needs declarative composition, shared app state, or React-first UI coordination. +- Use raw WebGL only for shader-heavy or renderer-first projects where engine abstractions would get in the way. +- Keep Babylon.js and PlayCanvas as alternative-engine paths rather than the default code-generation target. + +See `../../references/engine-selection.md` for the default decision table. + +## Implementation Checklist + +Define these before writing core code: + +- Player fantasy and primary verbs +- Core loop and loss or reset states +- Camera model +- Input action map +- Simulation modules +- Renderer modules +- Asset manifest layout +- 3D asset format and optimization rules +- HUD and menu surfaces +- Save data boundary +- Debug and perf surfaces + +## Anti-Patterns + +- Mixing gameplay rules directly into scene callbacks +- Treating the renderer as the source of truth for game state +- Putting all HUD and menu UI into the canvas by default +- Letting asset filenames become the public API instead of manifest keys +- Shipping unoptimized 3D assets straight from the DCC tool into the browser +- Mixing camera-control state and menu or modal state without an explicit input boundary +- Rebuilding architecture every time the game changes genre + +## References + +- Engine selection: `../../references/engine-selection.md` +- Phaser structure: `../../references/phaser-architecture.md` +- Three.js structure: `../../references/three-webgl-architecture.md` +- Three.js ecosystem stack: `../../references/threejs-stack.md` +- React Three Fiber stack: `../../references/react-three-fiber-stack.md` +- 3D asset shipping: `../../references/web-3d-asset-pipeline.md` diff --git a/.hermes/plugins/game-studio/skills/web-game-foundations/agents/openai.yaml b/.hermes/plugins/game-studio/skills/web-game-foundations/agents/openai.yaml new file mode 100644 index 00000000..a59dfd1c --- /dev/null +++ b/.hermes/plugins/game-studio/skills/web-game-foundations/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Web Game Foundations" + short_description: "Set browser-game architecture before implementation" + default_prompt: "Establish the core architecture for this browser game before implementation starts." From ac2cf78ffa828e1743333c897f3da9eda7f427c8 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 13:47:26 +0800 Subject: [PATCH 2/7] Fix SpacetimeDB wasm dependency boundary --- .hermes/shared-memory/pitfalls.md | 8 ++++++++ ...RKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md | 15 +++++++++++++++ server-rs/crates/api-server/Cargo.toml | 2 +- server-rs/crates/shared-contracts/Cargo.toml | 7 ++++++- server-rs/crates/shared-contracts/README.md | 1 + server-rs/crates/shared-contracts/src/lib.rs | 3 ++- 6 files changed, 33 insertions(+), 3 deletions(-) diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index edb0fdf5..c2f55aeb 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -107,6 +107,14 @@ - 验证:发布前完成 schema 检查、bindings 生成、表目录更新和相关 smoke。 - 关联:`docs/technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md`、`docs/technical/SPACETIMEDB_TABLE_CATALOG.md`。 +## SpacetimeDB publish 报 wasm-bindgen 时先查 shared-contracts feature + +- 现象:发布 `spacetime-module` 时报 `wasm-bindgen detected`,提示 `wasm-bindgen is only for webassembly modules that target the web platform`。 +- 原因:SpacetimeDB module 的 wasm32 构建树被间接带入原生/网页依赖;已验证链路是 `reqwest -> platform-oss -> shared-contracts -> module-runtime -> spacetime-module`,由共享契约默认启用资产 OSS 契约触发。 +- 处理:让 `shared-contracts` 的 OSS 资产契约走 `oss-contracts` feature,workspace 根依赖保持 `default-features = false`;`api-server` 这类原生后端需要资产 DTO 时在自身 `Cargo.toml` 显式启用 `features = ["oss-contracts"]`。 +- 验证:执行 `cargo tree -i wasm-bindgen --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown` 应显示 nothing to print;再执行 `cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml --target wasm32-unknown-unknown`。 +- 关联:`server-rs/crates/shared-contracts/Cargo.toml`、`server-rs/crates/api-server/Cargo.toml`、`docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md`。 + ## 本地 SpacetimeDB replica identity 不匹配 - 现象:本地 standalone 启动时报 `mismatched database identity`。 diff --git a/docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md b/docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md index 29fae1be..e3e7042b 100644 --- a/docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md +++ b/docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDATION_2026-05-07.md @@ -16,6 +16,7 @@ 4. 成员 crate 只保留自身需要表达的差异,例如 `features`、`optional = true` 或 target-specific dependency。 5. 需要关闭 default features 的依赖,应优先在 workspace 根依赖中声明;成员 crate 不再重复覆盖同一项。 6. `module-assets` 这类有默认服务端 feature 的领域 crate,在 workspace 根内按 `default-features = false` 维护;需要服务端 OSS/HTTP 能力的 adapter crate 显式启用 `features = ["server-service"]`。 +7. 面向 SpacetimeDB WASM 的依赖链不得隐式启用原生 HTTP / OSS / Web 平台依赖;例如 `shared-contracts` 的 `assets` 模块通过 `oss-contracts` feature 暴露给 `api-server`,`spacetime-module` 路径只消费关闭默认 feature 后的纯 DTO 子集。 ## 3. 本次收敛范围 @@ -53,3 +54,17 @@ npm.cmd run check:encoding -- docs/technical/RUST_WORKSPACE_DEPENDENCY_CONSOLIDA ``` 若仅改 Cargo 依赖配置且未触碰 API smoke 相关代码,不强制启动 `npm run api-server`;若后续改动同时涉及 API 路由、SpacetimeDB facade 或运行时行为,仍按 `AGENTS.md` 和 DDD 文档执行后端 smoke。 + +## 6. SpacetimeDB WASM 依赖边界 + +`spacetime publish` 会构建 `spacetime-module` 的 `wasm32-unknown-unknown` 目标。这个目标不能包含 `wasm-bindgen`,也不应通过 DTO crate 间接拉入 `reqwest`、`web-sys` 或浏览器 WebAssembly 平台依赖。 + +已验证的排查命令: + +```powershell +cargo tree -i wasm-bindgen --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown +cargo tree --manifest-path server-rs\crates\spacetime-module\Cargo.toml --target wasm32-unknown-unknown | Select-String -Pattern 'wasm-bindgen|platform-oss|reqwest' +cargo check -p spacetime-module --manifest-path server-rs\Cargo.toml --target wasm32-unknown-unknown +``` + +若反向树显示 `reqwest -> platform-oss -> shared-contracts -> module-* -> spacetime-module`,优先检查新增的 `shared-contracts` 或领域 crate 依赖是否忘记关闭默认 feature。原生 `api-server` 需要资产上传契约时,应在自身 `Cargo.toml` 显式启用 `shared-contracts` 的 `oss-contracts` feature,而不是让 workspace 根依赖默认启用。 diff --git a/server-rs/crates/api-server/Cargo.toml b/server-rs/crates/api-server/Cargo.toml index acaf6c2b..9dec401d 100644 --- a/server-rs/crates/api-server/Cargo.toml +++ b/server-rs/crates/api-server/Cargo.toml @@ -36,7 +36,7 @@ platform-oss = { workspace = true } platform-speech = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } -shared-contracts = { workspace = true } +shared-contracts = { workspace = true, features = ["oss-contracts"] } shared-kernel = { workspace = true } shared-logging = { workspace = true } spacetime-client = { workspace = true } diff --git a/server-rs/crates/shared-contracts/Cargo.toml b/server-rs/crates/shared-contracts/Cargo.toml index df973184..dabc1f73 100644 --- a/server-rs/crates/shared-contracts/Cargo.toml +++ b/server-rs/crates/shared-contracts/Cargo.toml @@ -4,7 +4,12 @@ edition.workspace = true version.workspace = true license.workspace = true +[features] +# 默认给 api-server 等原生后端保留资产上传契约;SpacetimeDB WASM 路径通过 workspace 依赖关闭默认 feature。 +default = ["oss-contracts"] +oss-contracts = ["dep:platform-oss"] + [dependencies] -platform-oss = { workspace = true } +platform-oss = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/server-rs/crates/shared-contracts/README.md b/server-rs/crates/shared-contracts/README.md index e6c1937d..ce9bfcb0 100644 --- a/server-rs/crates/shared-contracts/README.md +++ b/server-rs/crates/shared-contracts/README.md @@ -94,3 +94,4 @@ 1. `shared-contracts` 只放协议类型与兼容结构,不承接业务规则、供应商适配或状态写入逻辑。 2. 各模块 crate 对外暴露的协议优先复用这里的共享定义,避免重复散落。 3. 前端兼容契约一旦进入本 crate,就必须与任务清单和基线文档同步维护。 +4. `assets` 模块依赖 `platform-oss` 的稳定返回类型,默认通过 `oss-contracts` feature 给 `api-server` 使用;SpacetimeDB WASM 构建链路必须通过 workspace 依赖关闭默认 feature,避免把 `platform-oss` / `reqwest` / `wasm-bindgen` 带进 `spacetime-module`。 diff --git a/server-rs/crates/shared-contracts/src/lib.rs b/server-rs/crates/shared-contracts/src/lib.rs index 30613c10..5b99fe3c 100644 --- a/server-rs/crates/shared-contracts/src/lib.rs +++ b/server-rs/crates/shared-contracts/src/lib.rs @@ -1,13 +1,14 @@ pub mod admin; pub mod ai; pub mod api; +#[cfg(feature = "oss-contracts")] pub mod assets; pub mod auth; pub mod big_fish; pub mod big_fish_works; pub mod creation_agent_document_input; -pub mod creative_agent; pub mod creation_entry_config; +pub mod creative_agent; pub mod hyper3d; pub mod llm; pub mod match3d_agent; From 10ed4fa051adf88c6c47715784b744f00e715a70 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 14:27:33 +0800 Subject: [PATCH 3/7] docs: clarify SpacetimeDB root-dir usage --- .hermes/shared-memory/pitfalls.md | 16 ++--- .../genarrative-admin-backoffice/SKILL.md | 14 +---- .../dev-rust-stack-startup-2026-05-08.md | 12 ++-- .../private-table-sql-token-refresh.md | 4 +- AGENTS.md | 1 + ...OT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md | 6 +- docs/technical/README.md | 8 +-- ...ND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md | 14 ++--- ...ETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md | 2 +- ...EPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md | 18 +++--- ...RT_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md | 14 +++-- ..._SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md | 60 ++++++++++--------- ...OOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md | 2 + 13 files changed, 86 insertions(+), 85 deletions(-) diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index c2f55aeb..7d9c6345 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -118,17 +118,17 @@ ## 本地 SpacetimeDB replica identity 不匹配 - 现象:本地 standalone 启动时报 `mismatched database identity`。 -- 原因:root-dir / replica 数据残留与当前数据库身份不一致。 +- 原因:本地 SpacetimeDB 数据目录中的 replica 数据残留与当前数据库身份不一致。 - 处理:按本地 replica identity mismatch 文档进行备份、重建和脚本诊断。 - 验证:本地 SpacetimeDB 可正常启动并 publish / 访问。 - 关联:`docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md`。 -## 本地 SpacetimeDB publish 403 优先查 CLI root 身份 +## 本地 SpacetimeDB publish 403 优先查 CLI 身份和目标库 - 现象:`spacetime publish` 在 `Pre-publish check` 阶段返回 `403 Forbidden`,提示当前 identity 无权对目标 database identity 执行 `update database`。 -- 原因:裸 `spacetime` 命令使用了全局 CLI 登录态,但本地开发库权限绑定在 `server-rs/.spacetimedb/local` root-dir 的另一个身份上。 -- 处理:对比 `spacetime login show` 和 `spacetime --root-dir server-rs/.spacetimedb/local login show`;本地开发发布必须使用 `npm run dev:rust`,或显式追加 `--root-dir=server-rs/.spacetimedb/local`。 -- 验证:`spacetime --root-dir server-rs/.spacetimedb/local list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限的全局 identity。 +- 原因:当前 CLI 登录态不是目标数据库的创建者或授权身份,或 `.env.local` / publish 命令指向了另一个数据库或 SpacetimeDB 服务。 +- 处理:除 CI/CD 脚本内部受控用法外,不再使用 `spacetime --root-dir` 排障或发布。先执行 `spacetime login show`、`spacetime server list`,再用 `spacetime list --server http://127.0.0.1:3101` 或实际 `--server-url` 确认当前身份是否能看到目标库;本地开发发布优先使用 `npm run dev:rust` 或从 `server-rs` 目录执行显式 `--server` 的 `spacetime publish`。如果身份不对,重新登录正确身份、使用项目脚本重新生成本地库,或在 SpacetimeDB 侧补授权。 +- 验证:`spacetime list --server http://127.0.0.1:3101` 能看到目标库;重新发布不再使用无权限 identity。 - 关联:`scripts/dev-rust-stack.sh`、`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`。 ## `npm run dev` 本地 SpacetimeDB 401 / 403 可重置默认 local 身份 @@ -150,9 +150,9 @@ ## 本地 SpacetimeDB publish 401 可清本地库重发 - 现象:本地 `spacetime publish` 显示 `401` 无权限,或重新发布仍像是在更新旧库。 -- 原因:本地开发 root-dir 中保留的数据库、控制库身份或发布身份与当前目标不一致。 -- 处理:确认本地开发数据可以丢弃后,执行 `spacetime --root-dir=server-rs/.spacetimedb/local server clear`,再重新运行 `npm run dev` 或本地 publish。 -- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查 root-dir、库名和 CLI 身份。 +- 原因:本地开发数据目录中保留的数据库、控制库身份或发布身份与当前目标不一致。 +- 处理:确认本地开发数据可以丢弃后,停止本地 SpacetimeDB,备份或删除 `server-rs/.spacetimedb/local/data`,再重新运行 `npm run dev` 或本地 publish;不要用 `--root-dir` 手工清库。 +- 验证:重新发布日志应显示创建新的数据库,而不是更新旧数据库;若仍显示更新或继续 `401`,继续检查数据目录、库名和 CLI 身份。 - 关联:`docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md`、`docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md`。 ## Vite SPA fallback 吞掉 API 请求 diff --git a/.hermes/skills/genarrative-admin-backoffice/SKILL.md b/.hermes/skills/genarrative-admin-backoffice/SKILL.md index c710112a..0461fa47 100644 --- a/.hermes/skills/genarrative-admin-backoffice/SKILL.md +++ b/.hermes/skills/genarrative-admin-backoffice/SKILL.md @@ -165,15 +165,7 @@ npm run dev - `admin-web` 的 `/` 可能返回 302 跳转到 `/admin/`;验证前端时直接请求 `/admin/`。 - api-server 启动日志中 SpacetimeDB `127.0.0.1:3101` 连接被拒绝,不一定代表 api-server 没起来;只表示依赖的本地 SpacetimeDB 不可用。后台中需要读 SpacetimeDB 的页面(如埋点明细、表查询)要等 SpacetimeDB 可用后才能返回真实数据。 - `npm run dev` 依赖 `spacetime` CLI;先用 `command -v spacetime && spacetime --version` 确认可用。 -- WSL/Linux 下 SpacetimeDB CLI 2.2.0 使用项目内 `--root-dir` 时,standalone 可能会回调 `${root_dir}/bin/current/spacetimedb-cli`。如果报 `It seems like the spacetime version set as current may not exist` 或 `exec failed for .../.spacetimedb/local/bin/current/spacetimedb-cli`,把用户级安装同步到项目 root-dir: - -```bash -rm -rf server-rs/.spacetimedb/local/bin -mkdir -p server-rs/.spacetimedb/local/bin -cp -a ~/.local/share/spacetime/bin/2.2.0 server-rs/.spacetimedb/local/bin/2.2.0 -ln -sfn 2.2.0 server-rs/.spacetimedb/local/bin/current -server-rs/.spacetimedb/local/bin/current/spacetimedb-cli --version -``` +- 本地和人工排障不再使用 `spacetime --root-dir`。如果看到 `bin/current/spacetimedb-cli` 缺失类错误,优先确认是否仍在运行旧脚本或旧发布包;本地开发应使用 `npm run dev` / `npm run dev:rust`,通过项目脚本和 `--data-dir` 隔离 SpacetimeDB 数据目录,不再把用户级 SpacetimeDB 安装同步到项目目录。 - `scripts/dev-rust-stack.sh` 默认 `api timeout: 300s`. 合并 master 后首次 Rust 依赖/工作区重编译可能超过 300s,导致完整 `npm run dev` 在 api-server 就绪前超时并回收 SpacetimeDB。先让 Rust 编译完成,或临时用 `bash scripts/dev-rust-stack.sh --skip-spacetime --skip-publish --api-timeout-seconds 900` 预热 api-server 编译;之后再重新跑完整 `npm run dev`。 - 用户贴出的 Hermes background watch 通知可能来自已退出的旧 session。先用 `process poll` 查该 session 状态,再判断是否需要处理;不要把旧失败误判成当前服务失败。 @@ -219,7 +211,7 @@ npm install 5. `adminRoutes` 新增 route id 后,`AdminShell.routeIcons` 必须同步,否则 TypeScript 会因 `satisfies Record` 报错。 - 后台页面中的中文和 JSON 预览要避免整文件重写导致编码问题;修改后运行 `npm run check:encoding`。 - 后台数据页移动端要保证表格横向滚动,不要让整页布局撑坏。 -- 若用户追问“之前不是说要把 npm run dev 修好吗”这类已承诺的 dev 启动问题,不要只解释;先复现 `npm run dev`,再按启动日志修脚本并验证到服务就绪。WSL/Linux 下 `spacetime start --root-dir=server-rs/.spacetimedb/local` 可能需要把用户级 SpacetimeDB 版本目录同步到项目 root-dir 的 `bin/` 并建立 `bin/current`,详见 `references/dev-rust-stack-startup-2026-05-08.md`。 +- 若用户追问“之前不是说要把 npm run dev 修好吗”这类已承诺的 dev 启动问题,不要只解释;先复现 `npm run dev`,再按启动日志修脚本并验证到服务就绪。WSL/Linux 下本地开发应走 `spacetime start --data-dir=server-rs/.spacetimedb/local/data` 这一类数据目录隔离,不再用项目级 `--root-dir`,详见 `references/dev-rust-stack-startup-2026-05-08.md`。 - 涉及敏感配置、token、密码、连接串时,输出和文档中统一写 `[REDACTED]`。 ## 参考资料 @@ -230,4 +222,4 @@ npm install - `references/spacetimedb-http-sql-sats-display.md`:通过 HTTP SQL 读取 private table 时,enum / Option / Timestamp 的 SATS 原始 rows 如何转换为后台列表、详情和 Excel 可读值。 - `references/daily-login-tracking-trigger-points.md`:排查后台 `daily_login` 埋点为何不是登录接口写入,而是任务中心读取/领奖兜底写入的触发点记录。 - `references/daily-login-auth-closure.md`:将方案A拆出的每日登录埋点入口接入真实认证成功链路时的推荐接入点、非阻断语义、测试和提交注意事项。 -- `references/dev-rust-stack-startup-2026-05-08.md`:`npm run dev` / `scripts/dev-rust-stack.sh` 在 WSL/Linux 下同步 SpacetimeDB root-dir 安装、避免 `bin/current/spacetimedb-cli` 缺失和冷编译超时的修复记录。 +- `references/dev-rust-stack-startup-2026-05-08.md`:`npm run dev` / `scripts/dev-rust-stack.sh` 在 WSL/Linux 下改用用户级 SpacetimeDB CLI、项目数据目录和显式 publish server,避免项目级 `--root-dir` 与冷编译超时的修复记录。 diff --git a/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md b/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md index 61bcd34c..318e70de 100644 --- a/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md +++ b/.hermes/skills/genarrative-admin-backoffice/references/dev-rust-stack-startup-2026-05-08.md @@ -1,8 +1,8 @@ # `npm run dev` / `scripts/dev-rust-stack.sh` 启动修复记录 ## 症状 -- 多个 worktree 同时本地开发时,SpacetimeDB 数据库名可能相同,早期用项目级 `--root-dir` 隔离 CLI 状态来规避冲突。 -- 实测后确认:真正需要隔离的是 standalone 的 `data-dir`,不需要把 publish 也绑到项目级 root-dir。 +- 多个 worktree 同时本地开发时,SpacetimeDB 数据库名可能相同,早期曾用项目级 CLI root 隔离 CLI 状态来规避冲突。 +- 实测后确认:真正需要隔离的是 standalone 的 `data-dir`,不需要把 publish 也绑到项目级 CLI root。 - 早期脚本曾通过把用户级 SpacetimeDB 可执行文件目录同步到 `server-rs/.spacetimedb/local/bin/current` 来满足 standalone 回调需求,但这会把整套可执行文件复制进项目本地目录,维护成本高,也容易和用户级 CLI 版本漂移。 - 多个 worktree 同时启动时,SpacetimeDB 端口可能冲突;CLI 会询问是否使用最近的可用端口。 - 若 `npm run dev` 从仓库根目录直接执行 `spacetime publish --module-path server-rs/crates/spacetime-module`,内部 Cargo 不一定读取 `server-rs/.cargo/config.toml`,可能绕过 sccache/linker/target 缓存策略,表现为 spacetime-module 每次像全量重编译。 @@ -11,7 +11,7 @@ ## 当前方案 1. SpacetimeDB 可执行文件继续使用用户环境里的 `spacetime` 命令 - 启动 standalone 时不再复制 `spacetimedb-cli`、版本目录或 `bin/current`。 - - `spacetime start` 不再通过工程内 `--root-dir` 寻找可执行文件。 + - `spacetime start` 不再通过工程内 CLI root 寻找可执行文件。 2. 数据目录显式指定到项目本地 - 默认 `SPACETIME_DATA_DIR=${SERVER_RS_DIR}/.spacetimedb/local/data`。 - 启动命令使用 `spacetime start --data-dir "${SPACETIME_DATA_DIR}" --listen-addr ...`。 @@ -21,9 +21,9 @@ - 脚本向 `spacetime start` 发送回车,接受“最近可用端口”的默认建议。 - 随后从启动日志中的 `Starting SpacetimeDB listening on ...` 解析实际端口。 - 解析出的实际端口会覆盖 `SPACETIME_SERVER`,后续 publish、api-server、前端代理统一使用这个端口。 -4. publish 不再使用项目级 root-dir,但要从 `server-rs` 目录执行 +4. publish 不再使用项目级 CLI root,但要从 `server-rs` 目录执行 - 发布模块改为在 `server-rs` 下执行 `spacetime publish ... --server "${SPACETIME_SERVER}" ...`。 - - 这样 publish 使用用户级 CLI 默认身份/配置,不再依赖 worktree 内 root-dir。 + - 这样 publish 使用用户级 CLI 默认身份/配置,不再依赖 worktree 内 CLI root。 - 同时确保内部 Cargo 能读取 `server-rs/.cargo/config.toml`,复用项目级 sccache/linker/target 缓存策略,避免 `npm run dev` 比手动 publish 更容易触发慢速重编译。 5. 提高 api-server 就绪等待时间 - `API_SERVER_TIMEOUT_SECONDS` 保持 600,降低首次冷编译误判失败概率。 @@ -33,7 +33,7 @@ - 运行帮助检查:`bash scripts/dev-rust-stack.sh --help`,确认有 `--spacetime-data-dir`。 - 运行 `npm run dev` 后观察日志: - 输出 `spacetime data: .../server-rs/.spacetimedb/local/data`。 - - 不再出现同步/复制本机 SpacetimeDB 安装到项目 root-dir 的日志。 + - 不再出现同步/复制本机 SpacetimeDB 安装到项目 CLI root 的日志。 - SpacetimeDB 能正常监听,并输出 `spacetime actual: http://127.0.0.1:<实际端口>`。 - 若默认端口被占用,脚本应自动接受 SpacetimeDB 建议端口,并用实际端口发布模块、启动 api-server 和前端代理。 - 模块发布成功。 diff --git a/.hermes/skills/genarrative-admin-backoffice/references/private-table-sql-token-refresh.md b/.hermes/skills/genarrative-admin-backoffice/references/private-table-sql-token-refresh.md index 1dd7a829..3f6fe92d 100644 --- a/.hermes/skills/genarrative-admin-backoffice/references/private-table-sql-token-refresh.md +++ b/.hermes/skills/genarrative-admin-backoffice/references/private-table-sql-token-refresh.md @@ -7,7 +7,7 @@ 操作步骤: 1. 清空本地 SpacetimeDB 数据目录 - - 使用:`spacetime --root-dir= server clear --yes` + - 使用项目脚本停止本地实例后,备份或删除 `server-rs/.spacetimedb/local/data`。 - 只清本地开发环境,不要误伤远端或其他 worktree。 2. 启动本地 standalone @@ -19,7 +19,7 @@ - 只记录 identity,不要在日志中打印 token 明文。 4. 用新 token 登录 CLI - - 运行:`spacetime --root-dir= login --token ` + - 运行:`spacetime login --token ` - 这会把 token 写到本地 CLI 配置,后续 HTTP SQL 可读 private table。 5. 重新验证 SQL diff --git a/AGENTS.md b/AGENTS.md index e0a2dd76..5cb4c83b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,6 +40,7 @@ - 前端只做表现、交互和临时 UI 状态,不承接正式业务真相,不绕过后端投影或后端 API 直接实现业务规则。 - 修改后端代码后,按对应 DDD 文档中的验收命令执行测试;涉及 API smoke 时使用 `npm run api-server` 重新拉起后端并执行相应自动测试,同时确认 `/healthz`。 - `maincloud` / `Maincloud` / `MAINCLOUD` 相关脚本、环境变量、测试、文档要求和命名全部视为历史残留,禁止新增、运行或引用;若旧文档仍要求 `api-server:maincloud` 或 `GENARRATIVE_SPACETIME_MAINCLOUD_*`,以 [`docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md`](docs/technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md) 和本文件为准,并先修正文档口径。 +- 除 CI/CD 脚本内部受控用法外,人工命令、本地联调、排障步骤和文档示例禁止继续使用 `spacetime --root-dir`。本地数据隔离使用项目脚本或 `--data-dir`,发布目标必须显式传 `--server` / `--server-url`,身份问题通过同一 CLI 登录态、专用运行用户或显式 token 处理;若旧文档仍推荐 `--root-dir`,先修正文档口径再执行。 - 凡是涉及 SpacetimeDB 的设计、实现、脚本、调试、前端绑定接入,统一显式使用以下 skill 作为执行依据: - [$spacetimedb-cli](.codex\\skills\\spacetimedb-cli\\SKILL.md) - [$spacetimedb-rust](.codex\\skills\\spacetimedb-rust\\SKILL.md) diff --git a/docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md b/docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md index ffd2b6e2..89d9e231 100644 --- a/docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md +++ b/docs/technical/AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md @@ -67,9 +67,11 @@ HTTP status server error (503 Service Unavailable) 远端 `xushi-p4wfr` 挂起期间,抓大鹅本地体验应使用本地 SpacetimeDB: ```powershell -spacetime --root-dir=server-rs/.spacetimedb/local start --edition standalone --listen-addr 127.0.0.1:3101 +npm run dev:rust $env:GENARRATIVE_SPACETIME_MIGRATION_BOOTSTRAP_SECRET="codex-local-bootstrap-secret-20260501" -spacetime --root-dir=server-rs/.spacetimedb/local publish xushi-p4wfr --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module -c=on-conflict --yes +Push-Location server-rs +spacetime publish xushi-p4wfr --server http://127.0.0.1:3101 --module-path crates/spacetime-module -c=on-conflict --yes +Pop-Location ``` 再让 Rust API 指向本地库: diff --git a/docs/technical/README.md b/docs/technical/README.md index 21f8a808..913b62c3 100644 --- a/docs/technical/README.md +++ b/docs/technical/README.md @@ -79,7 +79,7 @@ - [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md):冻结 SpacetimeDB 表结构变更约束、自动迁移可接受范围、冲突后的系统行为,以及保留旧数据的增量迁移流程;凡涉及 `spacetime publish`、表字段调整或 `migration.rs` 对齐时优先参考。 - [PRODUCT_NAMING_BAIMENG_RENAME_2026-05-01.md](./PRODUCT_NAMING_BAIMENG_RENAME_2026-05-01.md):冻结当前对外中文命名,产品展示名统一为“百梦”,消费单位为“光点”,公开账号标识为“百梦号”,创作侧称谓为“百梦主”。 - [SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md](./SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md):记录旧云端 SpacetimeDB 配置、发布脚本和默认文档口径的移除结果,冻结后续仅使用本地或显式 `SERVER_URL` 的运维规则。 -- [SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md](./SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md):记录本地 standalone 启动时报 `mismatched database identity` 的 root-dir/replica 数据残留根因、备份重建步骤和脚本诊断口径。 +- [SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md](./SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md):记录本地 standalone 启动时报 `mismatched database identity` 的数据目录/replica 数据残留根因、备份重建步骤和脚本诊断口径。 - [AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md](./AUTH_SNAPSHOT_AND_MATCH3D_LOCAL_DEV_FIX_2026-05-01.md):记录远端库挂起导致认证快照同步和抓大鹅创作失败的根因、认证同步非阻断修复、`/api/creation` Vite 代理补齐和本地 SpacetimeDB 可跑链路。 - [LLM_MODEL_ROUTING_RPG_AND_CREATION_2026-04-30.md](./LLM_MODEL_ROUTING_RPG_AND_CREATION_2026-04-30.md):冻结 RPG 运行时剧情推理使用 `doubao-seed-character-251128` 的 `/chat/completions`,以及所有模板创作大模型推理使用 `deepseek-v3-2-251201` 的 `/responses`。 - [PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md](./PROFILE_INVITE_CODE_REGISTRATION_AND_ADMIN_2026-04-30.md):冻结邀请码从“我的 Tab 填写”迁到注册环节的前后端边界、`profile_invite_code.metadata_json` 表结构扩展、管理员邀请码虚拟主体和奖励规则。 @@ -113,11 +113,11 @@ - [RPG_SCENE_ACT_PREVIEW_BOOTSTRAP_FIX_2026-04-30.md](./RPG_SCENE_ACT_PREVIEW_BOOTSTRAP_FIX_2026-04-30.md):记录编辑器幕预览卡在“正在载入这一幕”时的启动态根因,收口预览本地运行态装配与禁持久化首段 story 注入。 - [PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md](./PUZZLE_RESULT_AUTOSAVE_AND_TAG_GATE_FIX_2026-04-28.md):记录拼图结果页名称与标签编辑自动保存、发布门槛统一到 `3~6` 标签,以及前端发布校验不再被旧 session blocker 卡死的修复口径。 - [WORK_AUTHOR_ID_RESOLUTION_2026-04-30.md](./WORK_AUTHOR_ID_RESOLUTION_2026-04-30.md):记录作品作者以 `owner_user_id` 为真相源,API 按用户 ID 解析最新昵称与公开用户码,历史 `author_display_name` 仅作为兼容回退。 -- [SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md](./SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md):记录发布包 `start.sh` 只输出“SpacetimeDB 进程在就绪前退出”时的诊断补强,启动失败或超时时自动回显 `logs/spacetimedb.log`、`server ping`、端口监听和 root-dir 相关进程。 +- [SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md](./SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md):历史事故记录,保留旧发布包 `start.sh` 只输出“SpacetimeDB 进程在就绪前退出”时的诊断补强;该文档不再作为当前发布或人工排障依据。 - [RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md](./RPG_AND_AGENT_CHAT_TRUE_SSE_STREAMING_2026-04-26.md):记录 RPG 运行时 NPC 聊天、RPG/自定义世界 Agent 与大鱼 Agent 从“拼完整 SSE 字符串后一次性返回”改为 `mpsc + Sse` 真流式输出的后端落地口径。 -- [SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md](./SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md):记录发布包 `start.sh` root-dir 占用检测把 `grep -F .../.spacetimedb` 误判为 SpacetimeDB 实例的根因、脚本修复和现场处理方式。 +- [SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md](./SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md):历史事故记录,保留旧发布包 `start.sh` 占用检测把 `grep -F .../.spacetimedb` 误判为 SpacetimeDB 实例的根因;该文档不再作为当前发布或人工排障依据。 - [RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md](./RPG_BATTLE_HEALTHBAR_AND_ACTION_PRESENTATION_FIX_2026-04-26.md):记录 RPG 战斗血条安全锚点、服务端战斗回包前端短表现,以及 `battle_use_skill` 指定技能兜底结算的修复口径。 -- [SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md](./SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md):记录发布包 `start.sh` 执行 `spacetime publish` 遇到 `403 Forbidden` 的身份根因、`.spacetimedb/` root-dir 隔离修复和排查步骤。 +- [SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md](./SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md):历史事故记录,保留旧发布包执行 `spacetime publish` 遇到 `403 Forbidden` 的身份根因;当前人工命令禁止使用 `spacetime --root-dir`,CI/CD 脚本内部受控用法除外。 - [SPACETIMEDB_TABLE_CATALOG.md](./SPACETIMEDB_TABLE_CATALOG.md):持续维护当前 SpacetimeDB 表目录,按领域说明每张表的作用、字段结构、索引和常用 `spacetime sql` 查询模板。 - [RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md](./RPG_OPENING_SCENE_ACT_IMAGE_PRESENTATION_SYNC_2026-04-26.md):记录开局场景与普通场景复用同一场景展示解析服务,修复列表幕缩略图和详情幕背景预览图片不一致的问题。 - [FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md](./FRONTEND_FIRST_LOAD_PERFORMANCE_FIX_2026-04-26.md):记录网站启动后首次加载约三分钟的前端根因,收口 `RouteImageReadyGate` 首屏图片门控和 Vite dev server 无关文件监听范围。 diff --git a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md index 9e8d3df5..81cff52e 100644 --- a/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md +++ b/docs/technical/RUST_LOCAL_AND_REMOTE_DEPLOYMENT_SCRIPTS_2026-04-22.md @@ -114,7 +114,7 @@ npm run dev:rust:logs -- --follow 日志提取规则: -1. SpacetimeDB 模块日志以 `spacetime --root-dir=server-rs/.spacetimedb/local logs ` 为唯一提取入口,脚本不直接读取内部日志文件结构。 +1. SpacetimeDB 模块日志以 `spacetime logs --server <实际本地 server>` 或 `npm run dev:rust:logs` 为提取入口,脚本不直接读取内部日志文件结构。 2. 默认读取 `spacetime.local.json` 的 `database` 字段,默认 server 为 `http://127.0.0.1:3101`。 3. 默认输出到 `logs/spacetime/-.log`,并通过 `tee` 同步显示在终端。 4. `--follow` 仅用于本地追踪,会持续追加到同一个输出文件;停止时用 `Ctrl+C`。 @@ -125,8 +125,8 @@ npm run dev:rust:logs -- --follow 2. `spacetime list --server http://127.0.0.1:3101` 应能看到 `spacetime.local.json` 中的库名;若没有,执行 `spacetime publish <本地数据库名> --server http://127.0.0.1:3101 --module-path server-rs/crates/spacetime-module --build-options="--debug" -c=on-conflict --yes`。 3. 发布库名与 `GENARRATIVE_SPACETIME_DATABASE` 不一致时,`/api/runtime/custom-world-gallery` 会从 Rust `api-server` 返回 `502`,前端首页只能展示空态或错误提示,无法自行修复。 4. 如果 Vite 输出 `/api/auth/refresh`、`/api/auth/login-options` 或 `/api/runtime/custom-world-gallery` 的 `ECONNREFUSED`,先确认当前脚本是否已经打印 `等待 api-server 就绪` 并通过;正常情况下 Vite 只会在 `/healthz` 可访问后启动,不应再因为 Rust 监听未完成而代理失败。 -5. 如果 `spacetime server ping` 打印 `Server could not be reached (502 Bad Gateway)`,即使命令退出码为 `0` 也不能直接视为已就绪;本地脚本会继续探测 `/v1/ping`。若 `/v1/ping` 返回 `200`,说明 standalone 已经可用,可以继续发布模块;若 `/v1/ping` 也失败,脚本会继续等待新启动实例,或在 root-dir 已被其他实例占用时输出占用进程。 -6. 如果本地 `spacetime publish` 显示 `401` 无权限,且确认本地开发数据可以丢弃,可执行 `spacetime --root-dir=server-rs/.spacetimedb/local server clear` 清除本地 SpacetimeDB 数据库后重新发布。重新发布时日志应表现为创建新的数据库,而不是更新旧数据库;如果仍显示更新旧库或继续无权限,说明 root-dir、库名或 CLI 身份仍未对齐。 +5. 如果 `spacetime server ping` 打印 `Server could not be reached (502 Bad Gateway)`,即使命令退出码为 `0` 也不能直接视为已就绪;本地脚本会继续探测 `/v1/ping`。若 `/v1/ping` 返回 `200`,说明 standalone 已经可用,可以继续发布模块;若 `/v1/ping` 也失败,脚本会继续等待新启动实例,或在本地数据目录已被其他实例占用时输出占用进程。 +6. 如果本地 `spacetime publish` 显示 `401` 无权限,且确认本地开发数据可以丢弃,先停止本地 SpacetimeDB,再备份或删除 `server-rs/.spacetimedb/local/data` 后重新运行 `npm run dev`。重新发布时日志应表现为创建新的数据库,而不是更新旧数据库;如果仍显示更新旧库或继续无权限,说明数据目录、库名或 CLI 身份仍未对齐。除 CI/CD 脚本内部受控用法外,人工清理不要使用 `spacetime --root-dir`。 7. Windows / Git Bash 下读取 `spacetime.pid` 或 `dev-rust-spacetime-url` 时,如果文件正被 SpacetimeDB 更新,不能用 `tr/head/xargs` 管道直接读;脚本使用 Node 读取并短重试,避免出现 `tr: read error: Device or resource busy` 后直接中断。 编译警告治理: @@ -166,12 +166,12 @@ npm run deploy:rust:remote 5. 执行 `cargo build -p spacetime-module --release --target wasm32-unknown-unknown --manifest-path server-rs/Cargo.toml`,并把 `spacetime_module.wasm` 复制到目标目录。 6. 把仓库根目录的 `.env` 与 `.env.local` 分别复制到目标目录根部和目标目录的 `web/` 下;复制后统一移除 UTF-8 BOM 与 CRLF,并把 `GENARRATIVE_SPACETIME_DATABASE` 覆盖为本次 `--database` 参数,避免 Jenkins 工作区里残留的旧 `.env.local` 覆盖发布包目标库。 7. 在目标目录写入 `web-server.mjs`,用于托管 `web/` 与 `web/admin/`;其中 `/admin` 跳转到 `/admin/`,`/admin/` 提供后台 SPA,`/admin/api/*`、`/api/*`、`/generated-*`、`/healthz` 反代到本包内的 `api-server`。 -8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env`、`.env.local`,兼容 UTF-8 BOM 与 CRLF,再回退到构建时通过 `--database`、`--api-port`、`--web-host`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,其中 Web 默认只监听 `127.0.0.1`;并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;同时按 Ubuntu 发布环境使用发布目录内 `.spacetimedb/` 作为 root-dir,不再额外设置 `--data-dir`,启动前先执行 `sync_ubuntu_spacetime_install`,优先从 `/usr/.local/share/spacetime/bin//spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin//spacetimedb-cli` 同步到 `.spacetimedb/bin/current/spacetimedb-cli`,当前线上 `spacetime` 入口为 `/usr/local/bin/spacetime`;启动参数为 `spacetime --root-dir ./.spacetimedb start --edition standalone --listen-addr :`,探活必须确认 `server ping` 输出包含 `Server is online:`;普通启动先无清库发布,若 publish 输出可判定为 schema 冲突,则自动导出旧库、清库发布新 wasm、导入回灌;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,代表人工确认清库,不触发自动回灌。 +8. 在目标目录写入 `start.sh` 与 `stop.sh`;`start.sh` 会先按 `KEY=value` 子集加载发布目录根部的 `.env`、`.env.local`,兼容 UTF-8 BOM 与 CRLF,再回退到构建时通过 `--database`、`--api-port`、`--web-host`、`--web-port`、`--spacetime-host`、`--spacetime-port` 写入的默认值,其中 Web 默认只监听 `127.0.0.1`;并默认导出 `NO_COLOR=1` 与 `CARGO_TERM_COLOR=never`,避免 ANSI 控制码写入日志文件;SpacetimeDB 启动、探活和发布由发布脚本内部统一编排。脚本内部如保留 `--root-dir`,只属于 CI/CD 或发布包受控用法,不作为人工命令模板。普通启动先无清库发布,若 publish 输出可判定为 schema 冲突,则自动导出旧库、清库发布新 wasm、导入回灌;如果以 `--clear-database` 启动,则内部 `spacetime publish` 会追加 `-c=on-conflict`,代表人工确认清库,不触发自动回灌。 9. 默认执行 `scp -r -i ~\.ssh\dsk.pem build/ ubuntu@82.157.175.59:/home/ubuntu/genarrative/` 上传发布包。 SpacetimeDB database 名称必须匹配 `^[a-z0-9]+(-[a-z0-9]+)*$`:只能使用小写字母、数字,并用单个短横线分隔;大写字母、点号、下划线、首尾短横线和连续短横线都会触发 `spacetime publish` 的 `invalid characters in database name`。发布包构建脚本和 `start.sh` 都会提前拦截这类非法名称。 -发布包构建日志会输出 `SpacetimeDB 发布数据库: `;目标服务器执行 `start.sh` 时会在发布前输出最终加载后的 `database/server/root-dir`,用于确认 `.env.local` 或 Jenkins 参数覆盖后的实际发布目标。 +发布包构建日志会输出 `SpacetimeDB 发布数据库: `;目标服务器执行 `start.sh` 时会在发布前输出最终加载后的 `database/server/数据目录或脚本运行目录`,用于确认 `.env.local` 或 Jenkins 参数覆盖后的实际发布目标。 发布包结构: @@ -225,8 +225,8 @@ cd build/ 5. 自动迁移导出旧库时优先读取 `deploy-state/migration-bootstrap-secret.previous.txt`,导入新库时读取当前发布包 `migration-bootstrap-secret.txt`;Jenkins 部署脚本会在覆盖发布包前保存旧密钥。该快照属于部署状态,不放入 `run/`,避免启停 hook 通过 `sudo` 运行后把部署阶段要写的文件变成 root 私有。手工覆盖发布包时,也应在覆盖前保留旧模块的引导密钥,否则旧库导出可能无法授权。 6. 自动迁移 JSON 默认写入发布目录下 `database-migrations//`;可通过 `GENARRATIVE_SPACETIME_MIGRATION_DIR` 改写。该目录属于运行态,不应被 Jenkins 覆盖部署删除。 7. 只有显式执行 `./start.sh --clear-database` 才追加 `-c=on-conflict`,该模式代表人工确认清库,不执行导出和回灌。 -8. `start.sh` 会先复用已经按目标地址就绪的 SpacetimeDB;如果同一个 `.spacetimedb/` root-dir 已被其他未就绪实例占用,则只输出命令名为 `spacetime` 或 `spacetimedb-cli` 且命令行包含当前 root-dir 的真实占用进程并失败,避免把排查用的 `grep` / `awk` 误判为 SpacetimeDB 实例。 -9. 如果 `spacetime publish` 报 `403 Forbidden`,优先确认 `spacetime --root-dir ./.spacetimedb login show` 输出的身份是否有权更新目标库;`--clear-database` 不能绕过身份授权。 +8. `start.sh` 会先复用已经按目标地址就绪的 SpacetimeDB;如果同一个 `.spacetimedb/` 运行目录已被其他未就绪实例占用,则只输出真实占用进程并失败,避免把排查用的 `grep` / `awk` 误判为 SpacetimeDB 实例。 +9. 如果 `spacetime publish` 报 `403 Forbidden`,优先确认 `spacetime login show` 输出的身份是否有权更新目标库,并确认 `GENARRATIVE_SPACETIME_DATABASE` / `GENARRATIVE_SPACETIME_SERVER_URL` 未指向错误环境;`--clear-database` 不能绕过身份授权。除 CI/CD 脚本内部受控用法外,人工排障不要使用 `spacetime --root-dir`。 10. 当前脚本是单目录进程启动方案,不替代生产 systemd、Nginx、TLS、日志轮转与守护进程配置。 11. 如只需要本地生成发布包,可传 `--skip-upload` 跳过默认 scp 上传。 diff --git a/docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md b/docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md index 0c092f05..82a6eb3d 100644 --- a/docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md +++ b/docs/technical/SPACETIMEDB_CLOUD_CONFIG_REMOVAL_2026-05-02.md @@ -39,4 +39,4 @@ GENARRATIVE_SPACETIME_TOKEN 1. 新增 SpacetimeDB 运维脚本时,不允许把云端服务写成默认值。 2. 文档中的验证命令统一使用 `npm run api-server`。 -3. 如果某次任务需要连接非本地 SpacetimeDB,必须在文档和验证记录中写清楚实际 `SERVER_URL`、数据库名和 root-dir。 +3. 如果某次任务需要连接非本地 SpacetimeDB,必须在文档和验证记录中写清楚实际 `SERVER_URL`、数据库名和身份来源;除 CI/CD 脚本内部受控用法外,不再把 `--root-dir` 写入人工命令。 diff --git a/docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md b/docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md index 8d3e5583..82916fd0 100644 --- a/docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md +++ b/docs/technical/SPACETIMEDB_LOCAL_REPLICA_IDENTITY_MISMATCH_FIX_2026-04-30.md @@ -16,17 +16,17 @@ error starting database: failed to init replica 1 for : m 2. `replica 1` 的持久化数据仍带有旧库 `c20037fcfaac4e5c4b1f492f026a4f6119a98f56319b77f21ef021ededf8b7ae`。 3. SpacetimeDB 因同一个副本目录中 identity 不一致而拒绝继续启动。 -这不是 Rust 编译错误,也不是 `api-server` 的 token 错误。只要错误来自 `server-rs/.spacetimedb/local/.../spacetime-standalone.log`,优先按本地 root-dir 数据目录污染处理。 +这不是 Rust 编译错误,也不是 `api-server` 的 token 错误。只要错误来自 `server-rs/.spacetimedb/local/.../spacetime-standalone.log`,优先按本地 SpacetimeDB 数据目录污染处理。 ## 2. 根因 -`spacetime start --edition standalone` 会在同一个 `--root-dir` 下保存控制库、程序字节、WAL 与 replica 数据。当前仓库默认本地 root-dir 是: +`spacetime start --edition standalone` 会在本地数据目录中保存控制库、程序字节、WAL 与 replica 数据。当前仓库默认本地数据目录是: ```text -server-rs/.spacetimedb/local +server-rs/.spacetimedb/local/data ``` -当这个目录曾经启动并发布过旧 database identity,之后又用同一个 root-dir 初始化或发布到另一个 database identity 时,可能出现: +当这个目录曾经启动并发布过旧 database identity,之后又用同一个数据目录初始化或发布到另一个 database identity 时,可能出现: 1. `control-db` 记录的是新库。 2. `data/replicas/1` 里仍残留旧库 WAL 或快照。 @@ -36,8 +36,8 @@ server-rs/.spacetimedb/local 1. 不在脚本里默认删除 `.spacetimedb` 数据,避免误删本地开发数据。 2. 如果只是本地开发库且数据可丢弃,优先备份后重建 `data` 目录。 -3. 如果数据必须保留,不要清理目录;应改回创建旧库时使用的 database/root-dir,或先导出迁移数据。 -4. 本地 standalone root-dir 与其它部署目标是两条链路;不要通过切回 `server-node` 或 PostgreSQL 绕过。 +3. 如果数据必须保留,不要清理目录;应改回创建旧库时使用的 database/server,或先导出迁移数据。 +4. 本地 standalone 数据目录与其它部署目标是两条链路;不要通过切回 `server-node` 或 PostgreSQL 绕过。 ## 4. 本地可丢弃数据时的修复 @@ -73,10 +73,10 @@ npm run dev:rust ## 5. 需要保留数据时的处理 -不要移动或删除 `server-rs/.spacetimedb/local/data`。先确认旧库 identity 对应的数据库名、root-dir 与发布命令,然后选择: +不要移动或删除 `server-rs/.spacetimedb/local/data`。先确认旧库 identity 对应的数据库名、server 与发布命令,然后选择: -1. 用旧库对应的 database/root-dir 重新启动。 -2. 使用迁移导出脚本导出旧数据,再清理本地 root-dir 并导入到新库。 +1. 用旧库对应的 database/server 重新启动或连接。 +2. 使用迁移导出脚本导出旧数据,再清理本地数据目录并导入到新库。 3. 如目标其实是其它已运行的 SpacetimeDB 服务,改用 `GENARRATIVE_SPACETIME_SERVER_URL` 指向该服务,避免误启动本地 standalone。 ## 6. 脚本诊断 diff --git a/docs/technical/SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md b/docs/technical/SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md index 5b49aa99..6598e7e2 100644 --- a/docs/technical/SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md +++ b/docs/technical/SPACETIMEDB_START_SH_EARLY_EXIT_DIAGNOSTICS_2026-04-27.md @@ -2,6 +2,8 @@ 日期:`2026-04-27` +状态:历史事故记录。本文针对旧发布包 `start.sh` 的诊断补强,相关 `start.sh` 运行细节已过时,不再作为当前发布或人工排障依据。当前 SpacetimeDB 人工命令不得使用 `--root-dir`,CI/CD 脚本内部受控用法除外。 + ## 1. 问题 执行发布包内 `start.sh` 时,可能只看到: @@ -16,10 +18,10 @@ ## 2. 常见根因 1. `GENARRATIVE_SPACETIME_PORT` 对应端口已被其他进程占用。 -2. `.spacetimedb/` root-dir 权限不正确,当前用户无法写入数据、bin 或日志目录。 +2. `.spacetimedb/` 运行目录权限不正确,当前用户无法写入数据或日志目录。 3. 目标机 `spacetime` 安装不完整,发布包同步不到可执行的 `bin/current/spacetimedb-cli`。 4. 目标机上的 `spacetime` 版本与脚本启动参数不兼容。 -5. 旧 SpacetimeDB 进程仍持有同一 root-dir 或数据锁,但当前 `GENARRATIVE_SPACETIME_SERVER_URL` 指向的端口未就绪。 +5. 旧 SpacetimeDB 进程仍持有同一运行目录或数据锁,但当前 `GENARRATIVE_SPACETIME_SERVER_URL` 指向的端口未就绪。 6. `.spacetimedb/bin/current/` 下只有 `spacetimedb-cli`,缺少 `spacetimedb-standalone`,日志会显示 `exec failed for .../spacetimedb-standalone`。 ## 3. 落地修复 @@ -31,11 +33,11 @@ 3. 当 SpacetimeDB 进程提前退出或等待超时时,自动打印: - `GENARRATIVE_SPACETIME_SERVER_URL` 对应的目标地址。 - `GENARRATIVE_SPACETIME_HOST:GENARRATIVE_SPACETIME_PORT` 对应的监听地址。 - - 当前 `GENARRATIVE_SPACETIME_ROOT_DIR`。 + - 当前 `.spacetimedb/` 运行目录。 - `logs/spacetimedb.log` 最近 120 行。 - `spacetime server ping` 的原始输出。 - `ss` 或 `netstat` 中当前端口的监听情况。 - - 同一 root-dir 下仍在运行的 SpacetimeDB 进程。 + - 同一运行目录下仍在运行的 SpacetimeDB 进程。 ## 4. 现场排查 @@ -43,7 +45,7 @@ ```bash tail -n 120 logs/spacetimedb.log -spacetime --root-dir ./.spacetimedb server ping "${GENARRATIVE_SPACETIME_SERVER_URL:-http://127.0.0.1:3101}" +spacetime server ping "${GENARRATIVE_SPACETIME_SERVER_URL:-http://127.0.0.1:3101}" ss -ltnp | grep ':3101' || true ``` @@ -54,7 +56,7 @@ exec failed for /var/lib/jenkins/deploy/Genarrative/.spacetimedb/bin/current/spa No such file or directory (os error 2) ``` -说明发布目录的 SpacetimeDB root-dir 中同步了 CLI,但没有同步 standalone。现场可先执行: +说明发布目录的 SpacetimeDB 运行目录中同步了 CLI,但没有同步 standalone。该段仅适用于仍保留 CI/CD 内部受控 `--root-dir` 的旧发布包;新人工排障不要引入 `--root-dir`。现场可先执行: ```bash cd /var/lib/jenkins/deploy/Genarrative diff --git a/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md b/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md index 3eb2419e..50999b3b 100644 --- a/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md +++ b/docs/technical/SPACETIMEDB_START_SH_PUBLISH_403_IDENTITY_FIX_2026-04-26.md @@ -1,7 +1,9 @@ -# start.sh 发布 SpacetimeDB 遇到 403 的处理方案 +# SpacetimeDB publish 遇到 403 的处理方案 日期:`2026-04-26` +状态:历史事故记录。本文涉及发布包 `start.sh` 的描述已过时,不再作为当前发布或排障入口。当前人工命令约束以 `AGENTS.md`、`.hermes/shared-memory/pitfalls.md` 和现行本地/生产技术文档为准;除 CI/CD 脚本内部受控用法外,不再使用 `spacetime --root-dir`。 + ## 1. 问题 执行发布包内 `start.sh` 时,`spacetime publish` 可能在 `Checking for breaking changes...` 后失败: @@ -23,42 +25,41 @@ SpacetimeDB 的数据库更新权限绑定到创建或被授权的身份。只 3. `GENARRATIVE_SPACETIME_SERVER_URL` 指向其它 SpacetimeDB 服务,而当前 CLI 身份不是该数据库的所有者或授权成员。 4. `.env.local` 中的 `GENARRATIVE_SPACETIME_DATABASE` 指向了另一个环境的数据库名或数据库 identity。 -## 3. 落地修复 +## 3. 当前处理口径 -发布包生成的 `start.sh` 使用发布目录下的 `.spacetimedb/` 作为 SpacetimeDB root: +除 CI/CD 脚本内部受控用法外,人工命令、本地联调、临时排障和文档示例不再使用 `spacetime --root-dir`。后续处理遵循: -```bash -GENARRATIVE_SPACETIME_ROOT_DIR="${SCRIPT_DIR}/.spacetimedb" -``` +1. 本地开发优先使用项目脚本 `npm run dev` / `npm run dev:rust`。 +2. 手工发布必须显式传 `--server` 或 `--server-url`,不依赖 CLI 默认 server。 +3. 身份问题通过同一 CLI 登录态、专用运行用户、显式 token 或 SpacetimeDB 侧授权处理。 +4. 本地数据隔离使用项目脚本维护的数据目录,必要时通过 `--data-dir` 启动 standalone;不要用 `--root-dir` 作为人工排障手段。 -启动、探活和发布统一使用: - -```bash -spacetime --root-dir="${GENARRATIVE_SPACETIME_ROOT_DIR}" ... -``` - -`spacetime start` 不再额外设置 `--data-dir`,启动前会先执行 Ubuntu 专用 `sync_ubuntu_spacetime_install`,优先从 `/usr/.local/share/spacetime/bin//spacetimedb-cli` 或 `$HOME/.local/share/spacetime/bin//spacetimedb-cli` 同步到 `.spacetimedb/bin/current/spacetimedb-cli`;当前线上 `spacetime` 入口为 `/usr/local/bin/spacetime`。启动参数、探活和 root-dir 占用判定都使用同一个 `.spacetimedb/`。这样可以把发布包与部署机全局 `~/.spacetime` 隔离,避免后续人工 `spacetime login` 影响本地发布包。但如果旧 `.spacetimedb/` 已经由另一个身份创建,仍需要按第 4 节处理。 - -本地 `npm run dev:rust` / `scripts/dev-rust-stack.sh` 也必须遵循同一条规则:`server ping`、`start` 和 `publish` 都显式使用 `server-rs/.spacetimedb/local` 作为 `--root-dir`。不要让发布命令回退到全局 CLI 登录态,否则会出现本地 root 已有目标库权限,但裸 `spacetime publish` 仍使用另一个身份发起预检查并返回 403。 +历史上曾用项目级 CLI root 隔离身份;该方案已废弃。CI/CD 脚本如因生产运行用户隔离仍保留内部受控用法,不得复制为人工命令模板。 ## 4. 排查与处理 先在执行 `start.sh` 的同一台机器、同一用户下确认身份: -```bash -spacetime --root-dir ./.spacetimedb login show -spacetime --root-dir ./.spacetimedb list --server http://127.0.0.1:3101 -``` - -本地开发栈排查时使用仓库本地 root: - ```bash spacetime login show -spacetime --root-dir server-rs/.spacetimedb/local login show -spacetime --root-dir server-rs/.spacetimedb/local list --server http://127.0.0.1:3101 +spacetime server list +spacetime list --server http://127.0.0.1:3101 ``` -如果裸 `spacetime login show` 的身份与 `--root-dir server-rs/.spacetimedb/local login show` 不一致,而目标库只出现在本地 root 的 `list` 结果中,说明不能使用裸 `spacetime publish`。应通过 `npm run dev:rust` 或显式追加 `--root-dir=server-rs/.spacetimedb/local` 重新发布。 +本地开发栈排查时确认项目脚本记录的实际 server,再用同一个 server 验证: + +```powershell +Get-Content -Encoding UTF8 server-rs/.spacetimedb/local/data/dev-rust-spacetime-url +spacetime login show +spacetime list --server http://127.0.0.1:3101 +``` + +如果 `spacetime login show` 的身份无权更新目标库,不要尝试通过 `--root-dir` 绕过。应选择以下路径之一: + +1. 重新登录目标库 owner 或被授权的身份。 +2. 在 SpacetimeDB 侧给当前身份补授权。 +3. 如果只是本地开发库且数据可丢弃,按 4.1 重置本地库后重新发布。 +4. 如果是连错服务或库名,修正 `.env.local` 中的 `GENARRATIVE_SPACETIME_DATABASE` / `GENARRATIVE_SPACETIME_SERVER_URL`。 ### 4.1 `npm run dev` 本地 401 / 403 快速恢复 @@ -91,7 +92,7 @@ spacetime login --server-issued-login local spacetime publish --server local A ``` -这条流程适合“本地 `npm run dev` 因旧 token、旧本地库或 CLI 默认 server 混乱导致无法继续”的场景。若当前目标库需要保留数据,不要执行 `spacetime server clear -y`,先按上一段对比 `login show`、`--root-dir` 和数据库所有者身份。 +这条流程适合“本地 `npm run dev` 因旧 token、旧本地库或 CLI 默认 server 混乱导致无法继续”的场景。若当前目标库需要保留数据,不要执行 `spacetime server clear -y`,先确认 `login show`、目标 server、数据库名和数据库所有者身份。 如果目标是本地部署库,且允许清空本地数据: @@ -105,7 +106,7 @@ mv .spacetimedb ".spacetimedb.backup.$(date +%Y%m%d-%H%M%S)" 1. 不要删除 `.spacetimedb/`。 2. 找到创建该数据库的 SpacetimeDB 身份。 -3. 用该身份对应的 CLI root 执行发布,或在 SpacetimeDB 侧补授权后再发布。 +3. 用该身份登录当前 CLI,或在 SpacetimeDB 侧补授权后再发布。 如果目标是其它 SpacetimeDB 服务: @@ -116,5 +117,6 @@ mv .spacetimedb ".spacetimedb.backup.$(date +%Y%m%d-%H%M%S)" ## 5. 约束 1. `--clear-database` 只处理 schema 冲突时的数据清理,不会绕过 SpacetimeDB 身份授权。 -2. 不要通过切回旧 `server-node` 或 PostgreSQL 绕过发布错误。 -3. 前端与 `api-server` 的数据库名必须和 `start.sh` 发布的库名一致,否则后续接口会连到未发布或无权限的库。 +2. 除 CI/CD 脚本内部受控用法外,不要在人工排障或文档示例中使用 `spacetime --root-dir`。 +3. 不要通过切回旧 `server-node` 或 PostgreSQL 绕过发布错误。 +4. 前端与 `api-server` 的数据库名必须和 `start.sh` 发布的库名一致,否则后续接口会连到未发布或无权限的库。 diff --git a/docs/technical/SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md b/docs/technical/SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md index 480477aa..3668212f 100644 --- a/docs/technical/SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md +++ b/docs/technical/SPACETIMEDB_START_SH_ROOT_OWNER_FALSE_POSITIVE_FIX_2026-04-27.md @@ -2,6 +2,8 @@ 日期:`2026-04-27` +状态:历史事故记录。本文只解释旧发布包 `start.sh` 的一次误报修复,相关 `root-dir` 检测不再作为当前发布或人工排障口径。当前人工命令不得使用 `spacetime --root-dir`,CI/CD 脚本内部受控用法除外。 + ## 1. 问题 执行发布包内 `start.sh` 时,可能出现: From fda996031f4ec1664947d1a942cfbbbaa3fd73ea Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 14:29:53 +0800 Subject: [PATCH 4/7] docs: add BDD skill --- .codex/skills/behavior-driven-development | 1 + .../behavior-driven-development/SKILL.md | 392 ++++++++++++++++++ 2 files changed, 393 insertions(+) create mode 120000 .codex/skills/behavior-driven-development create mode 100644 .hermes/skills/behavior-driven-development/SKILL.md diff --git a/.codex/skills/behavior-driven-development b/.codex/skills/behavior-driven-development new file mode 120000 index 00000000..31fbb4e7 --- /dev/null +++ b/.codex/skills/behavior-driven-development @@ -0,0 +1 @@ +C:/proj/Genarrative/.hermes/skills/behavior-driven-development \ No newline at end of file diff --git a/.hermes/skills/behavior-driven-development/SKILL.md b/.hermes/skills/behavior-driven-development/SKILL.md new file mode 100644 index 00000000..7d2f5a02 --- /dev/null +++ b/.hermes/skills/behavior-driven-development/SKILL.md @@ -0,0 +1,392 @@ +--- +name: behavior-driven-development +description: 在 Genarrative 中需要用 BDD/行为驱动方式把 PRD、用户故事、验收标准转成可执行场景、Gherkin 用例、测试计划或 TDD 落地顺序时使用。 +version: 1.0.0 +author: Hermes Agent +license: MIT +metadata: + hermes: + tags: [BDD, Gherkin, 验收标准, 用户故事, 测试, Genarrative] + related_skills: [writing-plans, test-driven-development, systematic-debugging, requesting-code-review] +--- + +# BDD 行为驱动开发流程 + +用于在 Genarrative 项目中,把产品需求、用户故事、业务规则和验收标准沉淀成清晰、可讨论、可验证的行为场景,并进一步映射到前端测试、API 测试、领域服务测试或 E2E 测试。 + +BDD 的重点不是“先写一堆 UI 自动化脚本”,而是让团队先对“用户在什么上下文下做什么,系统应该给出什么可观察结果”达成一致。 + +## 适用场景 + +- 从 PRD、设计文档、用户故事中提炼验收标准。 +- 功能需求容易产生理解偏差,需要先把行为边界说清楚。 +- 涉及前端、后端、运行态、异步任务、权限、埋点或状态流转的跨层功能。 +- 需要把“完成标准”写成 Given / When / Then 场景。 +- 需要在编码前规划测试覆盖:单元测试、组件测试、API 测试、E2E 测试。 +- 希望和 TDD 配合:先写行为场景,再把场景拆到 RED-GREEN-REFACTOR。 +- 需要给产品、测试、前后端开发共同评审的一份中文验收说明。 + +不适用: + +- 纯重构且对外行为不变,只需要 characterization tests 或回归测试。 +- 一次性小改动,验收规则非常明确且无跨层状态。 +- 只想记录实现细节、代码结构或技术方案;这类内容更适合技术设计文档。 + +## 必读约束 + +1. 先描述用户可观察行为,再讨论实现细节。 +2. 场景必须可验证,避免“体验更好”“更智能”“合理展示”等不可判定表述。 +3. 不要把 Gherkin 写成低层 UI 点击脚本;UI 细节只在确实属于业务行为时出现。 +4. 中文 PRD、中文 UI 文案、中文剧情和注释不要擅自改成英文。 +5. 如果项目文档不足以支持准确落地,应先补齐 `docs/` 下的 PRD/设计/技术文档,再进入编码。 +6. BDD 场景要覆盖主要成功路径、关键失败路径、权限/登录态、边界条件和回归风险。 + +## 核心格式 + +推荐使用中文 Gherkin: + +```gherkin +功能: <业务能力名称> + 为了 <用户/业务价值> + 作为 <角色> + 我希望 <能力> + + 背景: + 假如 <所有场景共享的前置条件> + + 场景: <具体行为名称> + 假如 <上下文/已有状态> + 当 <用户动作或系统事件> + 那么 <可观察结果> + 而且 <额外可观察结果> + + 场景大纲: <带参数的行为名称> + 假如 <上下文中包含 <变量>> + 当 <动作> + 那么 <结果> + + 例子: + | 变量 | 期望 | + | A | X | + | B | Y | +``` + +英文关键字也可以使用: + +```gherkin +Feature: Work publish permission + Scenario: Anonymous user attempts to publish a draft + Given an anonymous user has a generated draft + When the user clicks publish + Then the login modal should be shown + And the draft should remain unchanged +``` + +在 Genarrative 项目内,若参与评审的人主要使用中文,优先中文场景;测试框架要求英文命名时,可以保留中文场景标题并在测试文件中使用英文 describe/it。 + +## 从需求提炼 BDD 场景 + +### Step 1: 识别角色和业务目标 + +先回答: + +- 谁在使用?游客、已登录用户、创作者、管理员、审核人员、系统任务? +- 用户想完成什么?创建、生成、保存、发布、试玩、查看、兑换、导出? +- 业务价值是什么?降低创作门槛、保护权限、保证数据一致性、提升运营可见性? + +### Step 2: 抽取领域词汇 + +建立统一术语,避免同一概念多种叫法: + +- work / 作品 +- draft / 草稿 +- session / 创作会话 +- runtime / 运行态 +- publish / 发布 +- profile / 我的页签 +- invite code / 邀请码 +- analytics event / 埋点事件 + +场景中优先使用业务词,不要直接写组件名、函数名、数据库表名,除非这些就是用户可见对象。 + +### Step 3: 列出行为切片 + +按用户旅程切分: + +1. 入口是否出现、是否可点击。 +2. 进入页面或工作台后的初始状态。 +3. 用户提交输入后的成功路径。 +4. 失败路径:未登录、参数无效、权限不足、网络/API 失败、异步任务失败。 +5. 状态持久化:刷新、返回、重新进入、跨设备或重新登录。 +6. 对外副作用:保存、发布、埋点、通知、导出、生成资产。 +7. 回归风险:旧入口、旧数据、移动端布局、中文编码。 + +### Step 4: 把每个切片写成 Given / When / Then + +检查每个场景: + +- Given 只描述前置状态,不写动作过程。 +- When 只描述一个主要触发动作或事件。 +- Then 描述可观察结果,可以被测试或人工验收。 +- 一个场景只验证一个核心行为;不要把完整长流程塞进一个巨型场景。 + +## Genarrative 场景模板 + +### 前端入口 / 页面行为 + +```gherkin +功能: 我的页签反馈入口 + 为了让用户能从个人中心提交问题 + 作为已登录用户 + 我希望在我的页签打开独立的帮助与反馈页面 + + 场景: 已登录用户从我的页签进入反馈页面 + 假如用户已登录并停留在我的页签 + 当用户点击“帮助与反馈”入口 + 那么系统应进入独立的反馈页面 + 而且底部 tab 应保持选中“我的” + 而且页面不应展开在我的页签当前面板下方 + + 场景: 用户从反馈页面返回我的页签 + 假如用户正在反馈页面 + 当用户点击返回按钮 + 那么系统应回到平台主页面 + 而且当前 tab 应为“我的” +``` + +### 登录态 / 权限行为 + +```gherkin +功能: 需要登录的发布能力 + 为了保护作品归属和发布链路 + 作为游客 + 我不能在未登录时发布作品 + + 场景: 游客尝试发布生成草稿 + 假如游客已经生成一个草稿 + 当游客点击发布按钮 + 那么系统应打开登录弹窗 + 而且不应创建正式作品 + 而且草稿内容应保留在当前会话中 +``` + +### 后端 API / 领域规则 + +```gherkin +功能: 作品正式游玩开始埋点 + 为了统计不同玩法的正式游玩行为 + 作为数据分析人员 + 我希望每次用户进入正式作品运行态时记录统一事件 + + 场景大纲: 支持的玩法进入正式游玩 + 假如存在一个已发布的 <玩法> 作品 + 当用户从作品详情进入正式游玩 + 那么后端应记录 work_play_start 事件 + 而且 scope_kind 应为 work + 而且 metadata 应包含 playType、workId、sourceRoute 和 userId + + 例子: + | 玩法 | + | puzzle | + | match3d | + | square-hole | + | custom-world | + | big-fish | + | visual-novel | +``` + +### 异步生成 / SSE 行为 + +```gherkin +功能: AI 创作会话流式回复 + 为了让用户看到生成进度 + 作为创作者 + 我希望提交创作指令后能收到流式反馈并最终得到可编辑草稿 + + 场景: 成功生成草稿 + 假如用户已登录并创建了创作会话 + 当用户提交有效的创作指令 + 那么系统应开始展示流式回复 + 而且生成结束后应展示草稿结果 + 而且会话快照应包含最新用户输入和 AI 回复 + + 场景: 生成失败 + 假如用户已登录并创建了创作会话 + 当用户提交创作指令但后端生成失败 + 那么系统应展示可理解的失败状态 + 而且用户应能够重试 + 而且不应覆盖上一次成功生成的草稿 +``` + +## 映射到测试类型 + +| BDD 场景关注点 | 推荐测试层级 | 示例 | +| --- | --- | --- | +| 纯领域规则、状态机、校验 | Rust/TS 单元测试 | reducer、module-*、schema validator | +| DTO 契约、API 请求响应 | API/contract 测试 | Axum handler、shared-contracts serde | +| 页面渲染、按钮状态、表单校验 | 组件测试 | Vitest + Testing Library | +| 路由、tab、页面阶段切换 | 前端集成测试 | appPageRoutes、FlowShell 行为 | +| 登录态、发布、运行态完整链路 | E2E/smoke | Playwright 或项目 smoke 脚本 | +| 埋点、副作用、后台导出 | 后端集成/API 测试 | tracking event、admin export | + +原则: + +- 不是每个 BDD 场景都必须落成 E2E。 +- 能在低层稳定验证的规则,不要强行放到脆弱的浏览器自动化里。 +- E2E 只覆盖最关键的用户旅程和跨层集成风险。 + +## 与 TDD 的配合方式 + +BDD 先回答“行为是什么”,TDD 再推动“代码怎么长出来”。 + +推荐顺序: + +1. 写 BDD 场景,确认业务行为和验收标准。 +2. 给每个场景标注测试层级:unit / component / API / E2E。 +3. 选择一个最小场景进入 TDD。 +4. RED:先写失败测试,测试名称对应场景标题。 +5. GREEN:实现最小代码让测试通过。 +6. REFACTOR:清理重复、命名、边界和文档。 +7. 回到下一个场景,直到主要路径和关键失败路径覆盖。 + +测试命名建议: + +```ts +describe('帮助与反馈入口', () => { + it('已登录用户从我的页签进入独立反馈页面', () => { + // Given ... + // When ... + // Then ... + }) +}) +``` + +Rust 测试命名建议: + +```rust +#[test] +fn anonymous_user_cannot_publish_generated_draft() { + // Given + // When + // Then +} +``` + +## 推荐产物 + +根据任务复杂度选择产物位置。使用本 skill 产出 Gherkin/BDD 场景时,必须先决定落点,不要把正式验收场景随手写在聊天记录里。 + +### Gherkin/BDD 场景默认落点 + +| 产物类型 | 推荐路径 | 适用场景 | +| --- | --- | --- | +| 实施前分析 / 临时计划 | `.hermes/plans/-bdd-scenarios.md` | 某次 Hermes 开发任务前,用于澄清行为、拆测试、辅助实现;不一定作为长期产品依据。 | +| 正式产品验收 / PRD 场景 | `docs/prd/_BDD_YYYY-MM-DD.md` | 产品、测试、开发都需要长期参考的验收标准、用户故事、功能边界。 | +| 技术/API/领域行为场景 | `docs/technical/_BDD_YYYY-MM-DD.md` | 后端 API、领域规则、状态机、SpacetimeDB reducer/table、SSE/异步任务、埋点副作用。 | +| 自动化 Gherkin feature 文件 | `tests/features/*.feature` 或 `e2e/features/*.feature` | 项目已接入 Cucumber/Playwright BDD 等 Gherkin runner 时。未接入前不要随意新建测试 runner 目录。 | +| 稳定流程或团队经验 | `.hermes/shared-memory/` 或 `.hermes/skills/` | 不是某个功能验收,而是长期可复用的团队流程、坑点、执行规范。 | + +默认规则: + +1. 用户只说“先用 BDD 梳理一下/写场景/写 Gherkin”,默认写到 `.hermes/plans/-bdd-scenarios.md`。 +2. 用户说“正式验收标准/PRD/产品文档/给测试验收”,写到 `docs/prd/_BDD_YYYY-MM-DD.md`。 +3. 用户说“API 行为/后端规则/状态机/埋点/异步任务/SpacetimeDB”,写到 `docs/technical/_BDD_YYYY-MM-DD.md`。 +4. 用户明确要求“可执行 feature 文件”且项目已有 runner,再写 `.feature` 文件;否则先写 Markdown BDD 文档,并在测试映射中标注未来自动化落点。 +5. 如果 BDD 场景会作为编码依据,文档中必须包含“测试映射”表,标注场景要落到哪些测试文件。 + +命名建议: + +```text +.hermes/plans/profile-feedback-bdd-scenarios.md +docs/prd/PROFILE_FEEDBACK_BDD_2026-05-11.md +docs/technical/WORK_PLAY_TRACKING_BDD_2026-05-11.md +tests/features/profile-feedback.feature +e2e/features/invite-code.feature +``` + +### 其他配套产物 + +除 BDD/Gherkin 场景外,相关配套内容可放在: + +- 实施计划:`.hermes/plans/.md` +- 产品/验收文档:`docs/prd/_PRD_YYYY-MM-DD.md` +- 技术设计:`docs/technical/_TECHNICAL_YYYY-MM-DD.md` +- 共享经验或稳定流程:`.hermes/shared-memory/` 或 `.hermes/skills/` + +BDD 文档建议包含: + +```markdown +# <功能名> BDD 验收场景 + +## 背景 +- 需求来源: +- 相关文档: +- 相关入口/接口: + +## 角色与目标 +- 角色: +- 目标: +- 非目标: + +## 场景清单 + +### 功能: <能力> + +```gherkin +场景: <场景名> + 假如 ... + 当 ... + 那么 ... +``` + +## 测试映射 + +| 场景 | 测试层级 | 目标文件 | 状态 | +| --- | --- | --- | --- | +| ... | component | ... | planned | + +## 开放问题 +- ... +``` + +注意:上面的 Markdown 模板中如果嵌套代码块,需要在真实文档里调整围栏长度,避免代码块提前闭合。 + +## 评审检查清单 + +- [ ] 每个场景都有清晰角色或业务上下文。 +- [ ] Given / When / Then 没有混入过多实现细节。 +- [ ] Then 都是可观察、可测试、可人工验收的结果。 +- [ ] 覆盖成功路径、失败路径、权限/登录态、边界条件。 +- [ ] 明确哪些场景需要自动化,哪些只需人工验收。 +- [ ] 自动化测试层级合理,没有把所有行为都塞进 E2E。 +- [ ] 中文文案、剧情、注释、文档没有被无意翻译或改写成英文。 +- [ ] 涉及中文文件修改时计划运行编码检查。 + +## 常见坑 + +1. **把 BDD 写成 UI 操作流水账。** 例如“点击第一个按钮,再点第二个按钮”。应改为用户意图和业务结果。 +2. **Then 不可验证。** “体验更顺滑”不是验收标准;要写成加载状态、错误提示、数据状态、页面阶段等可观察结果。 +3. **一个场景塞太多断言。** 长流程应拆成多个小场景,避免失败时不知道真正坏在哪里。 +4. **只写 happy path。** Genarrative 常见风险在登录态、刷新恢复、异步失败、端口/后端不可用、旧数据兼容和移动端布局。 +5. **把实现方案当成业务规则。** “调用某函数”通常不是用户行为;除非是 API/技术验收,否则放到技术设计或测试实现里。 +6. **BDD 和 TDD 脱节。** 写完场景后要映射测试层级和目标文件,否则场景容易停留在文档层。 +7. **场景词汇不统一。** work、draft、session、runtime、publish 等概念要和项目现有文档/代码保持一致。 +8. **忽略文档先行约束。** 若 PRD 不足以编码落地,先补文档,再开始工程修改。 + +## 验证与收口 + +执行 BDD 相关任务后,至少确认: + +- [ ] 已产出或更新 BDD 场景文档/计划。 +- [ ] 场景已映射到具体测试层级和目标文件。 +- [ ] 若进入编码,已按 TDD 或等价方式先补测试。 +- [ ] 已运行相关验证命令,例如: + +```bash +npm run check:encoding +npm run typecheck +npm run test -- --run <相关测试文件> +``` + +- [ ] 若涉及后端 Rust/API,按相关 DDD/SpacetimeDB 文档运行对应 cargo/npm/API smoke 验证。 +- [ ] 若产生长期有效经验,已同步到 `.hermes/shared-memory/` 或合适的仓库级 skill。 From 928acb430220e9d0e47b470372aad3509c3d6229 Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 17:24:24 +0800 Subject: [PATCH 5/7] Extend sccache startup timeout for Windows builds --- .hermes/shared-memory/pitfalls.md | 8 +-- ...EDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md | 71 +++++++++++++++++-- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/.hermes/shared-memory/pitfalls.md b/.hermes/shared-memory/pitfalls.md index 7d9c6345..f3a582f1 100644 --- a/.hermes/shared-memory/pitfalls.md +++ b/.hermes/shared-memory/pitfalls.md @@ -383,10 +383,10 @@ ## Rust 构建不要让不可用的 sccache 阻断 rustc -- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。 -- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。 -- 处理:本地临时排障可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`;`npm run dev:rust` 的 SpacetimeDB publish 已在命中 sccache 通信失败时自动清空 wrapper 重试一次;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。 -- 验证:`rustc -Vv` 能输出版本;清空 wrapper 后 `cargo check --target=wasm32-unknown-unknown --release` 能通过;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。 +- 现象:Cargo 报 `could not execute process sccache ... rustc.exe -vV (never executed)`、`sccache: error: Timed out waiting for server startup`,或 `sccache: caused by: Failed to send data to or receive data from server / Failed to read response header / failed to fill whole buffer`;真实 `rustc -Vv` 可以执行,但构建在调用包装器时失败。 +- 原因:环境、Jenkinsfile 或 `server-rs/.cargo/config.toml` 启用了 `sccache` wrapper,但当前 agent 没有可执行的 `sccache`、PATH 中 shim 损坏,或本地 sccache server/client 通道状态损坏。Windows 本机若配置了 `SCCACHE_OSS_*`,sccache daemon 冷启动会先经 OSS/本机代理完成缓存读写检查,再监听 `127.0.0.1:4226`;代理或 OSS 链路慢时,Cargo 的 `sccache rustc -vV` 可能先超时。 +- 处理:保留 `server-rs/.cargo/config.toml` 的 `rustc-wrapper = "sccache"`;Windows 本机优先在 `%APPDATA%\Mozilla\sccache\config\config` 写入 `server_startup_timeout_ms = 60000`,拉长 client 等待 daemon 完成 OSS 初始化的时间,然后删除 `server-rs/target/.rustc_info.json` 里缓存的失败探测结果并重跑原始 Cargo 命令。冷启动验证优先用 `sccache --stop-server`,不要在另一个 `cargo` / `rustc` 仍在编译时 `taskkill /F /IM sccache.exe /T`,否则 proc-macro crate 可能被打断并表现为 `serde_derive` / `spacetimedb-bindings-macro` 的 `sccache ... exit code: 1`。若只做临时排障,可在 Git Bash 中执行 `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo build ...`,或在 PowerShell 用 `cargo check -p api-server --config "build.rustc-wrapper=''"` 一次性绕过 wrapper;生产流水线必须先实际执行 `sccache --version`,失败时移除 `RUSTC_WRAPPER` 并回退到直接 `rustc`。 +- 验证:`rustc -Vv` 能输出版本;冷启动后原始 `cargo check -p api-server` 和 `cargo check -p spacetime-module` 能通过;`sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,证明仍在使用 sccache/OSS 缓存;Jenkins 日志出现“未找到可用 sccache,改用 rustc 直接构建”后仍继续真实构建。 - 关联:`scripts/dev-rust-stack.sh`、`jenkins/Jenkinsfile.production-stdb-module-build`、`docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md`、`docs/technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md`。 ## 生产发布入口不要沿用旧 Jenkinsfile / 一体化脚本 diff --git a/docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md b/docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md index 3340dbdb..6eb83f67 100644 --- a/docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md +++ b/docs/technical/SPACETIMEDB_PUBLISH_SCCACHE_FALLBACK_2026-05-09.md @@ -7,6 +7,7 @@ Windows 本地执行 `npm run dev:rust` 或 `spacetime publish` 时,`spacetime 当本机 sccache server 状态损坏、client/server 通信异常或版本残留不一致时,可能出现: ```text +sccache: error: Timed out waiting for server startup. Maybe the remote service is unreachable? sccache: error: failed to execute compile sccache: caused by: Failed to send data to or receive data from server sccache: caused by: Failed to read response header @@ -15,9 +16,24 @@ sccache: caused by: failed to fill whole buffer 这类错误发生在 rustc wrapper 层,不能说明 SpacetimeDB module 代码本身编译失败。 +## 2026-05-11 本机根因定位 + +本机 `cargo check -p api-server` 失败时,Cargo 还没有进入业务 crate 编译,而是在读取 `server-rs/.cargo/config.toml` 后执行 `sccache rustc -vV` 探测编译器版本。失败的 stderr 会被写入 `server-rs/target/.rustc_info.json`,内容为 `Timed out waiting for server startup`。 + +当前 PowerShell 环境设置了 `SCCACHE_OSS_BUCKET=genarrative-sccache`、`SCCACHE_OSS_ENDPOINT=https://oss-rg-china-mainland.aliyuncs.com` 和 `SCCACHE_OSS_KEY_PREFIX=genarrative`,且没有设置本地 `SCCACHE_DIR`。因此 sccache daemon 冷启动时会先初始化 OSS 远端缓存,并执行 `.sccache_check` 的读写检查;日志中可见 `Init oss cache ...`、`proxy(http://127.0.0.1:7897/) intercepts ...`,随后才出现 `server started, listening on 127.0.0.1:4226`。 + +本次排查的结论是:冷启动失败主要发生在 sccache client 等待 daemon 启动的握手窗口内,而 daemon 启动又依赖 OSS/本机代理链路先完成缓存可读写检查。代理或 OSS 链路稍慢时,Cargo 调用的 `sccache rustc -vV` 会先超时;daemon 预热后直接执行同一条 `sccache rustc -vV` 又可能成功,所以这是冷启动/通道状态问题,不是 `api-server` 或 Rust 代码错误。 + +辅助证据: + +1. `rustc -vV` 可直接输出版本,说明 Rust 工具链本身可用。 +2. `tasklist` 曾只看到 `sccache --show-stats` 客户端进程,`netstat` 只出现到 `127.0.0.1:4226` 的 `SYN_SENT`,没有真正的 `LISTEN`,说明当时 client 正在等一个尚未成功监听的 daemon。 +3. 在子进程中临时清掉 `SCCACHE_OSS_*` 并设置本地 `SCCACHE_DIR` 后,sccache 退回本地磁盘缓存,日志显示 `Init disk cache ...`,`rustc -vV` 和 `sccache --show-stats` 均能完成。 +4. `C:\Users\DSK\AppData\Roaming\Mozilla\sccache\config\config` 缺失只是非致命 warning,本机实际配置来自环境变量。 + ## 本地开发处理 -`scripts/dev-rust-stack.sh` 的 publish 阶段继续由 SpacetimeDB CLI 内部调用 Cargo,并通过 `--build-options="--debug"` 使用 debug 构建参数。遇到 sccache 通信或 wrapper 失败时,本地排障仍优先绕过 wrapper 验证 rustc 本身可用。 +`scripts/dev-rust-stack.sh` 的 publish 阶段继续由 SpacetimeDB CLI 内部调用 Cargo,并通过 `--build-options="--debug"` 使用 debug 构建参数。遇到 sccache 冷启动超时时,优先保留 `sccache` wrapper,并修复 sccache daemon 的启动等待时间;只有在排除 sccache 本身问题时,才临时绕过 wrapper 验证 rustc 本身可用。 该处理不修改 `server-rs/.cargo/config.toml`,也不删除本地 target 缓存。 @@ -29,13 +45,56 @@ sccache: caused by: failed to fill whole buffer rustc -vV ``` -如果只想绕过本次 Cargo 构建的 sccache wrapper,可在 Git Bash 中执行: +如果要保留 sccache 并修复冷启动等待时间,在 PowerShell 中创建或更新 sccache 默认配置: + +```powershell +$configDir = Join-Path $env:APPDATA "Mozilla\sccache\config" +New-Item -ItemType Directory -Force -Path $configDir | Out-Null +@( + "# Windows 本机 sccache 冷启动需要先完成 OSS 缓存读写检查。" + "# 拉长 client 等待 daemon 启动的时间,避免 Cargo 在 rustc -vV 阶段误判超时。" + "server_startup_timeout_ms = 60000" +) | Set-Content -Encoding UTF8 -Path (Join-Path $configDir "config") +``` + +随后清掉 Cargo 曾缓存的失败探测结果,并从冷启动验证: + +```powershell +cd C:\proj\Genarrative\server-rs +sccache --stop-server +Remove-Item -Force target\.rustc_info.json -ErrorAction SilentlyContinue +cargo check -p api-server +``` + +注意:不要在另一个 `cargo` / `rustc` 仍在编译时执行 `taskkill /F /IM sccache.exe /T`。sccache 对 proc-macro crate 会显示 `Server sent UnhandledCompile` 并把请求转交给真实 rustc;如果此时强杀 sccache client/server,可能让 `serde_derive`、`spacetimedb-bindings-macro` 等 proc-macro 编译直接以 `sccache ... exit code: 1` 失败,而 stderr 里看不到真正的 Rust 诊断。这是排障动作打断编译,不是 `spacetime-module` 源码错误。 + +如果只想临时绕过本次 Cargo 构建的 sccache wrapper,可在 Git Bash 中执行: ```bash cd server-rs/crates/spacetime-module RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo check --target=wasm32-unknown-unknown ``` +PowerShell 原生 Cargo 的一次性 wrapper 绕过命令是: + +```powershell +cd C:\proj\Genarrative\server-rs +cargo check -p api-server --config "build.rustc-wrapper=''" +``` + +如果需要验证是否为 OSS/代理冷启动问题,可只在当前 PowerShell 进程中切到本地缓存做对照: + +```powershell +$env:SCCACHE_LOG = "debug" +$env:SCCACHE_ERROR_LOG = "C:\proj\Genarrative\logs\sccache-local-start-error.log" +$env:SCCACHE_DIR = Join-Path $env:TEMP "genarrative-sccache-local-test" +Remove-Item Env:SCCACHE_OSS_BUCKET -ErrorAction SilentlyContinue +Remove-Item Env:SCCACHE_OSS_ENDPOINT -ErrorAction SilentlyContinue +Remove-Item Env:SCCACHE_OSS_KEY_PREFIX -ErrorAction SilentlyContinue +sccache "C:\Users\DSK\.rustup\toolchains\stable-x86_64-pc-windows-msvc\bin\rustc.exe" -vV +sccache --show-stats +``` + 如果需要排查 sccache server 状态: ```bash @@ -44,10 +103,12 @@ sccache --stop-server sccache --start-server ``` -`sccache --stop-server` 本身也可能因为 server 通道已损坏而失败;此时不应阻断本地开发 publish,先使用 wrapper 降级完成验证。 +`sccache --stop-server` 本身也可能因为 server 通道已损坏而失败;只有确认当前没有 `cargo`、`rustc`、`link` 进程后,才用 `taskkill /F /IM sccache.exe /T` 清理残留进程。此时不应阻断本地开发 publish,先使用 wrapper 降级完成验证。 ## 验证 1. `bash -n scripts/dev-rust-stack.sh` -2. `RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= cargo check --target=wasm32-unknown-unknown` -3. 重新运行 `npm run dev:rust`,确认 publish 命令带有 `--build-options="--debug"`。 +2. 冷启动后直接执行 `cargo check -p api-server`,确认不再出现 `Timed out waiting for server startup`。 +3. 执行 `cargo check -p spacetime-module`,确认 proc-macro 依赖和 SpacetimeDB module 都能在 sccache wrapper 下通过。 +4. `sccache --show-stats` 显示 `Cache location oss, name: genarrative-sccache`,确认仍在使用 sccache/OSS 缓存。 +5. 重新运行 `npm run dev:rust`,确认 publish 命令带有 `--build-options="--debug"`。 From 7cea41c911d856905922ee2f041fb04bd1fc8ded Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 11 May 2026 18:00:36 +0800 Subject: [PATCH 6/7] Add frontend debug mode gate --- .env.example | 4 ++ .hermes/shared-memory/decision-log.md | 8 +++ ...IME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md | 6 ++ .../PuzzleRuntimeShell.test.tsx | 50 +++++++++++++++- .../puzzle-runtime/PuzzleRuntimeShell.tsx | 57 ++++++++++++++----- src/config/debugMode.ts | 23 ++++++++ src/vite-env.d.ts | 4 ++ 7 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 src/config/debugMode.ts diff --git a/.env.example b/.env.example index e8b61440..74c656dc 100644 --- a/.env.example +++ b/.env.example @@ -173,6 +173,10 @@ VITE_SCENE_IMAGE_REQUEST_TIMEOUT_MS="150000" # Keep this off by default for cleaner logs. VITE_LLM_DEBUG_LOG="false" +# Optional: global frontend debug mode. When empty, it follows Vite dev mode. +# Set to "true" to expose local diagnostic panels, or "false" to hide them. +VITE_DEBUG_MODE="" + # Optional: official VikingDB credentials for regenerating build-tag similarities # with the Python embedding script. The script auto-loads `.env.local` and uses # the fixed `bge-large-zh` embedding model. diff --git a/.hermes/shared-memory/decision-log.md b/.hermes/shared-memory/decision-log.md index ce5249ab..2d0c0c6d 100644 --- a/.hermes/shared-memory/decision-log.md +++ b/.hermes/shared-memory/decision-log.md @@ -32,6 +32,14 @@ - 验证方式:执行 `npm run test -- src\services\input-devices\runtimeDragInputController.test.ts`、`npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。 - 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`、`docs/technical/PUZZLE_RUNTIME_FRONTEND_LOGIC_REHOME_2026-05-02.md`。 +## 2026-05-11 前端调试模式统一判断 + +- 背景:拼图 mocap 调试面板此前在运行态常驻展示,生产构建和正式体验里容易遮挡棋盘内容;后续其它局部诊断 UI 也需要统一的调试模式入口。 +- 决策:前端新增 `src/config/debugMode.ts` 作为全局调试模式判断,默认跟随 Vite 开发态,允许 `VITE_DEBUG_MODE=true/false` 显式覆盖。拼图运行态 mocap 调试面板只在调试模式下渲染,并默认折叠,只保留连接状态行。 +- 影响范围:前端局部调试 UI、拼图运行态 mocap 诊断面板、`.env.example` 和运行态输入技术文档。 +- 验证方式:执行 `npm run test -- src\components\puzzle-runtime\PuzzleRuntimeShell.test.tsx`、`npm run typecheck` 和编码检查。 +- 关联文档:`docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md`。 + ## 2026-05-10 儿童动作热身关直接消费 mocap 数据源 - 背景:儿童动作 Demo 不能只依赖浏览器摄像头状态和键鼠调试输入,否则真实硬件接入后会出现“mocap 在线但页面提示摄像头不可用”或“能看到画面但动作不推进”的卡点。 diff --git a/docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md b/docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md index d7e00cfc..fc03df58 100644 --- a/docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md +++ b/docs/technical/RUNTIME_INPUT_DEVICE_ABSTRACTION_2026-05-10.md @@ -26,6 +26,12 @@ - mocap 光标按 60Hz 插值更新 UI 位置,并在拖拽中用插值后的当前点持续驱动输入层,避免输入包帧率低或抖动时出现明显跳变。 - 合并大块由拼图运行态把手部坐标命中到任一成员拼块;本地拼图运行时再按 `mergedGroupId` 执行整组平移。 +## 调试模式 + +前端全局调试模式统一通过 `src/config/debugMode.ts` 判断。默认跟随 Vite 开发态:`import.meta.env.DEV` 为真时开启,生产构建默认关闭;如需显式覆盖,可设置 `VITE_DEBUG_MODE=true` 或 `VITE_DEBUG_MODE=false`。 + +拼图运行态的 mocap 调试面板只在全局调试模式下渲染。面板默认折叠,只保留一行连接状态,展开后才显示动作、手势、解析告警和原始包预览,避免开发诊断信息遮挡拼图棋盘和底部操作。 + ## 接入规则 新玩法或新设备接入时遵循以下边界: diff --git a/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx b/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx index 283c874b..51043905 100644 --- a/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx +++ b/src/components/puzzle-runtime/PuzzleRuntimeShell.test.tsx @@ -1,7 +1,7 @@ /* @vitest-environment jsdom */ import { act, fireEvent, render, screen, within } from '@testing-library/react'; -import { expect, test, vi } from 'vitest'; +import { beforeEach, expect, test, vi } from 'vitest'; import type { PuzzleRunSnapshot } from '../../../packages/shared/src/contracts/puzzleRuntimeSession'; import { AuthUiContext } from '../auth/AuthUiContext'; @@ -31,6 +31,15 @@ const mocapMock = vi.hoisted(() => ({ y: 0.58, })); +const debugModeMock = vi.hoisted(() => ({ + enabled: true, +})); + +vi.mock('../../config/debugMode', () => ({ + IS_DEBUG_MODE: debugModeMock.enabled, + isDebugMode: () => debugModeMock.enabled, +})); + vi.mock('../../services/useMocapInput', () => ({ useMocapInput: () => ({ status: 'connected', @@ -44,6 +53,13 @@ vi.mock('../../services/useMocapInput', () => ({ }), })); +beforeEach(() => { + debugModeMock.enabled = true; + mocapMock.state = 'grab'; + mocapMock.x = 0.42; + mocapMock.y = 0.58; +}); + function createAuthValue() { return { user: null, @@ -157,7 +173,7 @@ const clearedRun: PuzzleRunSnapshot = { }, }; -test('拼图界面显示 mocap 连接状态和最近动作调试信息', () => { +test('调试模式下拼图界面折叠展示 mocap 连接状态,展开后显示最近动作调试信息', () => { renderPuzzleRuntime( { const debugPanel = screen.getByTestId('puzzle-mocap-debug'); expect(within(debugPanel).getByText('mocap: connected')).toBeTruthy(); + const toggleButton = within(debugPanel).getByRole('button', { + name: 'mocap: connected', + }); + expect(toggleButton.getAttribute('aria-expanded')).toBe('false'); + expect(within(debugPanel).queryByText('动作: grab')).toBeNull(); + + fireEvent.click(toggleButton); + + expect(toggleButton.getAttribute('aria-expanded')).toBe('true'); expect(within(debugPanel).getByText('动作: grab')).toBeTruthy(); expect(within(debugPanel).getByText('手势: grab @ 0.42, 0.58')).toBeTruthy(); expect(within(debugPanel).getByText('解析: 无')).toBeTruthy(); expect(within(debugPanel).getByText(/原始:/)).toBeTruthy(); }); +test('非调试模式下拼图界面不渲染 mocap 调试面板', () => { + debugModeMock.enabled = false; + renderPuzzleRuntime( + , + ); + + expect(screen.queryByTestId('puzzle-mocap-debug')).toBeNull(); +}); + test('拼图界面在 mocap open_palm 时显示体感光标', () => { mocapMock.state = 'open_palm'; mocapMock.x = 0.42; diff --git a/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx b/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx index 3125ae89..f6f082d9 100644 --- a/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx +++ b/src/components/puzzle-runtime/PuzzleRuntimeShell.tsx @@ -1,6 +1,8 @@ import { ArrowLeft, ArrowRight, + ChevronDown, + ChevronUp, Clock, Eye, Lightbulb, @@ -22,6 +24,7 @@ import type { PuzzleRuntimePropKind, SwapPuzzlePiecesRequest, } from '../../../packages/shared/src/contracts/puzzleRuntimeSession'; +import { isDebugMode } from '../../config/debugMode'; import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl'; import { createRuntimeDragInputController, @@ -361,6 +364,7 @@ export function PuzzleRuntimeShell({ const [isFreezeEffectVisible, setIsFreezeEffectVisible] = useState(false); const [isPropConfirming, setIsPropConfirming] = useState(false); const [propConfirmError, setPropConfirmError] = useState(null); + const [isMocapDebugExpanded, setIsMocapDebugExpanded] = useState(false); const [hintDemo, setHintDemo] = useState(null); const [mergeFlash, setMergeFlash] = useState( null, @@ -462,6 +466,7 @@ export function PuzzleRuntimeShell({ ? mocapInput.latestCommand.parseWarnings.join(';') : '无'; const mocapRawPacketLabel = mocapInput.rawPacketPreview?.text ?? '未收到'; + const shouldShowMocapDebugPanel = isDebugMode(); useEffect(() => { currentLevelRef.current = currentLevel; @@ -1744,19 +1749,45 @@ export function PuzzleRuntimeShell({ 已选择 ) : null} -
-
mocap: {mocapInput.status}
-
动作: {mocapActionsLabel}
-
手势: {mocapHandLabel}
-
解析: {mocapParseWarningLabel}
-
- 原始: {mocapRawPacketLabel} -
- {mocapInput.error ?
错误: {mocapInput.error}
: null} -
+ {shouldShowMocapDebugPanel ? ( +
+ + {isMocapDebugExpanded ? ( +
+
动作: {mocapActionsLabel}
+
手势: {mocapHandLabel}
+
解析: {mocapParseWarningLabel}
+
+ 原始: {mocapRawPacketLabel} +
+ {mocapInput.error ?
错误: {mocapInput.error}
: null} +
+ ) : null} +
+ ) : null} {canShowNextAction ? (