新规hook
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# CLAUDE.md
|
||||
|
||||
本文件为 Claude Code(claude.ai/code)在本仓库中工作时提供指导。
|
||||
本文件为 Claude Code(claude.ai/code)在本仓库中工作时提供指导。**它是入口与强制指引**:三套文档的 README(含各自「红线速查 / 一页纸总则」)已通过 `@import` 常驻上下文(见下「文档」一节),**红线以它们为权威、始终在上下文里,本文件不再另抄一份**;规范的完整细节在 `docs/` 编号文档里,改动前先读对应文档。
|
||||
|
||||
## 这是一个什么仓库
|
||||
|
||||
@@ -8,116 +8,70 @@
|
||||
|
||||
`client/js/01_SubGame/codes/` 和 `server/games` 目前都是**空的**——尚未接入任何具体子游戏。本仓库是平台层 + 可复用框架的脚手架,未来第一个子游戏会在此基础上搭建。
|
||||
|
||||
本仓库是**模板项目**——后续会被克隆出去开新项目。因此所有需要长期保留的约定与知识**一律进仓库**(`docs/` 权威文档、本 CLAUDE.md、`.claude/` 钩子与 `settings.json`、`.githooks/`),**不依赖 Claude memory**(memory 按机器/会话存放、不随 `git clone` 迁移,对模板克隆无效)。
|
||||
|
||||
## 常用命令
|
||||
|
||||
没有配置任何构建/lint 工具。仓库里的脚本:
|
||||
没有配置任何构建/lint/测试工具。仓库里唯一的脚本:
|
||||
|
||||
```bash
|
||||
# 根据 client/assets/spine/*.json 重新生成 client/generated/spine_assets.js 与 spine_data.js
|
||||
client/scripts/build_spine_data.cmd # 内部调用 build_spine_data.ps1
|
||||
|
||||
# 二七王服务端单元测试(纯 node 直跑,无框架)
|
||||
node server/games/erqiwang/test/run.js # 跑全部;退出码 0 全过、非 0 有失败
|
||||
```
|
||||
|
||||
在 `client/assets/spine/` 增删 Spine 导出文件后运行第一条;不要手改这两个生成文件。
|
||||
在 `client/assets/spine/` 增删 Spine 导出文件后运行;不要手改这两个生成文件。
|
||||
|
||||
查看客户端效果需用 HTTP 方式(而非 `file://`)打开 `client/index.html`,例如 `npx http-server client -p 8080`。
|
||||
|
||||
服务端子游戏代码遵循「一套代码,两个运行时」(见 `docs/server/development-guide/01` §1):友乐/浏览器无 `require`、由 `min_loadJsFile` 加载为全局;Node(本地/单元测试)用文件顶部 `if (typeof require!=='undefined')` 守卫 require、底部 `module.exports` 导出。二七王据此已可用 `node` 直接单测(见 `server/games/erqiwang/test/README.md`);`shared/`(共享算法)改动同样按两份文档「§10 测试纪律」跑 Node 单测验证。
|
||||
**启用提交前机械红线校验**(严格 ES5、可编辑范围等硬红线的 git 提交闸)——`.githooks/pre-commit` 已随仓库提交,但 `core.hooksPath` 是本地配置、不随克隆迁移,故**每个新克隆需一次性**执行:
|
||||
|
||||
## 架构
|
||||
|
||||
### 两套独立代码库,一份线上协议
|
||||
|
||||
- `client/` —— 浏览器端,严格 ES5,由 gameabc canvas 引擎驱动。
|
||||
- `server/` —— Node.js,严格 ES5,即 `youle` 游戏平台。
|
||||
- 两端交换的 JSON 包结构固定为 `{ app: "youle", route, rpc, data }`:`route` 决定投递到服务端哪个模块,`rpc` 决定调用该模块上的哪个方法(`mod[pack.rpc](pack)`——没有二次 `switch(action)` 分发,一个操作对应一个 RPC 方法)。**服务端把结果告知客户端的唯一可靠方式是主动推送**(`o_room.method.sendpack_toseat/toother`);`DoPack` 的返回值并不是真正的下发通道。成败判定**只**看推送包里的 `data.success`(布尔值)——绝不用 `status`/`code`。
|
||||
|
||||
### 客户端分层(`client/js/`,依赖关系即 `index.html` 里的加载顺序)
|
||||
|
||||
```
|
||||
js/vendor/ 第三方与引擎(gameabc.min.js、jquery、spine-canvas)—— 禁止修改
|
||||
js/00_Surface/ 平台代码 —— 禁止修改
|
||||
js/gameabc-framework/ 可复用、游戏中立的框架(见下)—— 禁止渗入任何具体玩法的逻辑
|
||||
core/ GameABCUtils(唯一允许直接调用 gameabc/引擎原生 API 的模块)、SpriteManager(做 ID 校验的业务级精灵 API)
|
||||
system/ EventBus(空的 Events{} 容器——具体玩法事件由子游戏自行追加,框架不预置)、SpriteEventController、SpriteGestureRecognizer、AnimationManager、AudioManager
|
||||
ui/ BaseComponent、UIManager、SpriteCopyUtils、DynamicSpriteList、RecordView(及其默认配置)、AlignmentUtils
|
||||
spine/ SpineMgr —— 必须在 gameabc.min.js 之后、gamemain.js/gameenddraw 被(重新)定义之前加载
|
||||
network/RpcHelper —— 自动给外发请求注入平台字段(agentid/gameid/playerid/roomcode/seat)
|
||||
templates/ *.template.js —— 供新子游戏「复制填充」的起点模板;不会被 index.html 直接引用
|
||||
js/01_SubGame/
|
||||
00_SubGame_Config.js / 01_SubGame_modify.js / 02_SubGame_Input.js 固定的平台契约文件(见下)
|
||||
codes/ 子游戏自己的代码(尚未创建);codes/shared/ 将是 server/<游戏>/shared/ 的只读同步副本
|
||||
```bash
|
||||
git config core.hooksPath .githooks
|
||||
```
|
||||
|
||||
依赖方向严格单向:`01_SubGame/codes` → `gameabc-framework` → `gameabc.min.js`。框架不反向依赖任何子游戏。**`index.html` 里的加载顺序就是依赖关系图**——新增文件必须插在其依赖之后、使用者之前,否则运行时会拿到 `undefined`(这里没有模块系统)。
|
||||
(Claude Code 内 `.claude/` 的 PreToolUse 钩子会自动生效、无需此步;这一步只为让**提交闸**对所有工具/人手改也生效。测试脚本 `tests/`、`*.test.js`、`*.spec.js` 允许现代语法,不受严格 ES5 拦截;只有正式代码严格 ES5。)
|
||||
|
||||
`shared/`(共享算法,待子游戏接入后才有)的改动应按两套文档「§10 测试纪律」的要求跑 Node 单测验证——目前尚未接入测试框架,意味着需要直接用 `node` 运行相应脚本。
|
||||
|
||||
## 可编辑范围
|
||||
|
||||
> 架构骨架(前端分层 / 服务端运作模型 / 工程七大总则)、成败协议、子游戏接入两模式等,均在下方 `@import` 常驻的三份 README「一页纸」与对应编号文档里,本文件不复述。此处只留改动前最需在手边的一张表(完整规则见各 README「红线速查」):
|
||||
|
||||
可编辑范围:
|
||||
| 路径 | 规则 |
|
||||
|---|---|
|
||||
| `js/vendor/`、`js/00_Surface/` | 禁止修改 |
|
||||
| `js/vendor/`、`js/00_Surface/`、`server/` 平台代码 | 禁止修改 |
|
||||
| `01_SubGame/00_SubGame_Config.js` | 仅可改**已有** `Game_Config.*` 配置项的值——禁止新增/删除/改名/改结构 |
|
||||
| `01_SubGame/01_SubGame_modify.js` | 仅可填顶部「配置区」(`Type_1/Type_2/CreateRoomData/combat/game_config/roomDes`);转发壳部分不碰 |
|
||||
| `01_SubGame/02_SubGame_Input.js` | 完全不碰 |
|
||||
| `js/gameabc-framework/` | 可改,但须保持游戏中立(不含具体玩法常量/逻辑) |
|
||||
| `01_SubGame/codes/`(除 `shared/`) | 子游戏自由开发区 |
|
||||
| `01_SubGame/codes/`(除 `shared/`)、`server/<游戏容器目录>/<游戏>/` | 子游戏自由开发区 |
|
||||
| `01_SubGame/codes/shared/` | 服务端 `shared/` 的只读同步副本;改服务端一侧后再同步 |
|
||||
|
||||
子游戏有两种接入方式(见 `docs/client/development-guide/06`):**内联模式**(旧——业务逻辑直接写进三个契约文件)或 **Hooks 外置模式**(新——三个契约文件退化为纯转发壳,查 `SubGameHooks.X` 并委托;子游戏逻辑全部放在 `codes/SubGameHooks.js` + `codes/`)。每个子游戏二选一,不能混用;Hooks 外置模式的模板位于 `gameabc-framework/templates/subgame-entry/`。
|
||||
## 文档(改动前先读——这三套是权威源)
|
||||
|
||||
### 服务端分层(`server/`)
|
||||
本仓库开发**必须严格遵守**以下三套文档;改哪块就先读对应 README 与编号文档,而不是从代码反推约定:
|
||||
|
||||
```
|
||||
server/packet.js、applist.js 顶层收包入口 / 应用注册表 —— 禁止修改
|
||||
server/class/class.app.js|class.mod.js 三层路由:app(按 pack.app)→ mod(按 pack.route)→ method(按 pack.rpc)
|
||||
server/youle/ "youle" 应用
|
||||
server_room/class.room.js o_room(seatlist[]、sendpack_toseat/toother)
|
||||
server_room/class.player.js o_player(每座位一个,持有用于定向发包的 conmode/fromid)
|
||||
server_room/class.export.js|import.js 平台自身的 export/import 服务(check_player、deduct_roomcard、save_grade……)
|
||||
server/<游戏容器目录>/<游戏>/ ← 子游戏接入后**唯一**可编辑的区域(目录名并非框架强制固定;"games2" 只是早期的一种约定)
|
||||
mod.js 创建模块(cls_mod.new(modname, routename, youle_app)),按依赖顺序加载文件,把 RPC 方法定义为「提参 → 委托给 handler」的薄入口
|
||||
export.js 平台按需回调的可选接口(get_needroomcard、get_asetcount、makewar、get_deskinfo、get_disbandRoom、player_enter/leave……)
|
||||
import.js 对平台 4 个服务的薄封装:check_player、deduct_roomcard、save_grade、finish_gametask
|
||||
```
|
||||
- `docs/client/development-guide/`(01→06)、`docs/server/development-guide/`(01→04)——项目专属的平台接入规范与红线。
|
||||
- `docs/games/engineering/`(01→03)——位于两者之上、与平台无关的工程方法论(SSOT、单向依赖、职责单一、OCP、配置优先于硬编码、显式失败优于隐式兜底)。
|
||||
|
||||
每个房间的状态挂在 `o_room.o_desk.data.*` 上(在 `export.makewar` 内创建,且必须建立 `o_room.o_desk ⇄ o_desk.o_room` 双向引用)。**对局状态禁止用模块级单例/全局变量存放**——服务端在同一进程内并发跑多个房间;缓存/定时器/决策表必须以 `房间 + seat` 为 key(而非仅 `seat`),并在小局结束/解散/开新局时清理干净。
|
||||
三者互补不冲突:engineering 讲「该怎么设计、怎么长久演进」,dev-guide 讲「平台怎么接、红线是什么」;硬红线冲突时以 dev-guide 为准。
|
||||
|
||||
服务端代替玩家执行的「自动」操作(AI 托管、超时等)必须走与真人操作完全相同的 handler → 广播链路,这样客户端永远不需要为它们单开一套解析分支。
|
||||
以下三份 README(含各自「红线速查 / 一页纸总则」)通过 `@import` **自动加载进每次会话的上下文**——红线以它们为权威,无需在本文件另抄一份;改动具体区域时再深入读对应编号文档:
|
||||
|
||||
### 文档(改动前先读)
|
||||
@docs/client/development-guide/README.md
|
||||
@docs/server/development-guide/README.md
|
||||
@docs/games/engineering/README.md
|
||||
|
||||
`docs/client/development-guide/`(01→06 编号)与`docs/server/development-guide/`(01→04 编号)是本项目遵循的权威、项目专属开发指南——改哪块就读对应编号的文档,而不是从代码反推约定。`docs/games/engineering/` 是位于两者之上的、与平台无关的工程方法论层(单一权威数据源 SSOT、单向依赖、职责单一、对扩展开放对修改封闭 OCP、配置优先于硬编码、显式失败优于隐式兜底)。
|
||||
各篇「编号 ↔ 主题」见上面 `@import` 的三份 README 的「阅读顺序 / 阅读导航」表,此处不再复述。
|
||||
|
||||
具体子游戏「二七王」(`server/games/erqiwang/`)另有两份权威文档,改动该子游戏前必读:
|
||||
具体子游戏「二七王」(server/games/erqiwang/)另有两份权威文档,改动该子游戏前必读:
|
||||
|
||||
- `server/games/erqiwang/docs/design/design.md` —— **二七王玩法规则的唯一权威说明,必须严格遵守**。牌局构成、主牌顺序、叫分坐庄、出牌/跟牌/甩牌、捡分扣底、算子升级、算奖、房间选项等一切玩法规则以此为准;代码实现与该文档冲突时,属于代码缺陷,应改代码去符合规则(除非该规则项标注为「待确认」),**不得反过来改规则去迁就代码**。
|
||||
- `server/games/erqiwang/docs/protocol/packet_protocol.md` —— 二七王前后端收发包协议与包数据定义,**必须完全符合子游戏服务器代码实现(`server/games/erqiwang/*.js`)**。方向与 design.md 相反:协议文档是对既有代码行为的如实记录,两者不一致时以**代码为准**——应修改本协议文档去适配代码;代码新增/删除包或字段时,也要同步补全/删除本文档对应条目。
|
||||
- server/games/erqiwang/docs/design/design.md —— *二七王玩法规则的唯一权威说明,必须严格遵守*。牌局构成、主牌顺序、叫分坐庄、出牌/跟牌/甩牌、捡分扣底、算子升级、算奖、房间选项等一切玩法规则以此为准;代码实现与该文档冲突时,属于代码缺陷,应改代码去符合规则(除非该规则项标注为「待确认」),*不得反过来改规则去迁就代码*。
|
||||
|
||||
两套文档中反复强调的硬性规则(文档记载均对应过真实事故):
|
||||
- 全面严格 ES5(`var`/`function`,继承用 `Object.create`;禁止 `let/const`/箭头函数/模板字符串/`class`/解构/`Promise`)。
|
||||
- 服务端 `require` 只能写在文件开头的守卫块内(`if (typeof require !== 'undefined') { var X = require(...); }`)——禁止在函数体内中途 `require`;两个运行时之后统一按同一个全局名引用。
|
||||
- 成败判定只看推送包上的 `data.success`——绝不用 `status`/`code`,也不能写两者都判的兼容兜底。
|
||||
- 对影响发牌/庄家/手牌/计分/重连的权威数据,禁止用默认值掩盖缺失(`|| 0`、`|| []`、`|| ''`)——应直接显式报错;默认值仅允许用于纯展示/日志字段。
|
||||
- 一个职能只在一个模块实现——需要该能力时调用其所属模块,而不是在别处重新实现一份。
|
||||
- server/games/erqiwang/docs/protocol/packet_protocol.md —— 二七王前后端收发包协议与包数据定义,*必须完全符合子游戏服务器代码实现(server/games/erqiwang/\*.js**)***。方向与 design.md 相反:协议文档是对既有代码行为的如实记录,两者不一致时以*代码为准*——应修改本协议文档去适配代码;代码新增/删除包或字段时,也要同步补全/删除本文档对应条目。
|
||||
|
||||
## 测试纪律
|
||||
|
||||
与 `docs/client/development-guide/05` §10、`docs/server/development-guide/04` §10 一致,是本项目对测试代码的硬性要求:
|
||||
## 测试与 Git
|
||||
|
||||
- **测试代码可以不守严格 ES5**:严格 ES5 是为了兼容线上浏览器/友乐运行时;测试只跑在 Node(本地/CI),不上线,不受此限制,可用现代语法写测试。
|
||||
- **正式代码(待测代码)禁止出现专为测试服务的逻辑**:不允许在核心业务代码里写"仅单测传 X 时……"之类的分支、专供测试用的钩子/后门、为了可测性而放宽的校验。
|
||||
- **严禁为了让测试通过而修改正式代码**:除非确认是正式代码本身有业务缺陷(这种情况按缺陷修复处理,而不是"为了兼容测试");否则正式代码的行为不因测试而改变。
|
||||
- **严禁为了测试通过率而降低测试标准**:不允许 skip/条件 return、软化断言、放宽阈值、try-catch 吞异常、把硬断言改成"存在才校验"等任何掩盖手段。
|
||||
- **方向唯一**:正式代码服务生产,不服务测试——测试要适配正式代码的生产契约(真实的输入形状、真实的调用方式),而不是让正式代码迁就测试的简化输入/不规范 stub。
|
||||
- **失败先裁根因**:测试失败时先用证据(打印中间态、最小复现)裁定是【业务代码缺陷】还是【测试脚本缺陷】,禁止凭猜测下结论;业务缺陷修业务、脚本缺陷修脚本,两者都保持/恢复硬断言。
|
||||
- **用例要全面**:新增或修改测试时,正面用例(正常路径/合法输入)、反面用例(非法输入/错误路径/异常分支)、边界用例(空值、极值、临界条件、越界)三类都要覆盖,不能只测 happy path。
|
||||
|
||||
## Git 提交
|
||||
|
||||
本仓库已初始化 git(见根目录 `.gitignore`)。提交纪律与 `docs/server/development-guide/04-开发规范与红线.md` §11 一致:
|
||||
|
||||
- **及时提交,不堆积**:每完成一个可独立成立的逻辑改动(一个修复/一个功能点/一次重构/一批相关文档改动)就提交,不要把多个不相关改动攒成一次大提交。
|
||||
- **可以自动提交,无需每次都问**:完成一个阶段性改动后可直接创建 commit,不必逐次向用户确认;`push` 仍按需(涉及推送远程、force-push 等仍按常规安全规范处理)。
|
||||
- **提交信息用中文**,简明说明「做了什么/为什么」,一次提交聚焦一件事,结尾保留 `Co-Authored-By` 署名行。
|
||||
- 仍遵守通用安全底线:不用 `git add -A`/`git add .` 囫囵提交(防止误收敏感文件/大文件),不跳过 hooks,不做 `reset --hard`/`push --force` 等破坏性操作。
|
||||
- **测试纪律**:细则见 client 05 §10 / server 04 §10。要点——测试代码可用现代语法(只跑 Node,不上线);**正式代码禁止为测试而加逻辑/放宽校验**,禁止为过测而改正式代码或降低测试标准(skip、软化断言、吞异常等);失败先用证据裁定「业务缺陷 vs 脚本缺陷」;正面/反面/边界用例都要覆盖。
|
||||
- **Git 提交**(细则见 server 04 §11):完成一个可独立成立的逻辑改动即可**自动提交、无需逐次确认**(`push` 按需);提交信息用中文、聚焦一件事、结尾保留 `Co-Authored-By` 署名;不用 `git add -A`/`git add .`、不跳过 hooks、不做 `reset --hard`/`push --force`。
|
||||
|
||||
Reference in New Issue
Block a user