Files
erqiwang_youle/CLAUDE.md
T
joywayerandClaude Opus 5 911cc9cc23 CLAUDE.md:补充测试纪律说明
明确测试代码可不守 ES5、正式代码禁止为测试服务、禁止为通过率降标准、
测试失败先裁根因、用例需覆盖正反面与边界。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-01 18:34:43 +08:00

11 KiB
Raw Blame History

CLAUDE.md

本文件为 Claude Code(claude.ai/code)在本仓库中工作时提供指导。

这是一个什么仓库

一个友乐/gameabc 房卡类小游戏平台:浏览器端由私有的 gameabc.min.js 2D Canvas 引擎驱动,服务端是一个 Node.js 游戏服务器平台(youle 应用),双方通过 WebSocket/HTTP 收发 JSON 包通信。项目根目录没有 package.json、没有 npm 构建/lint/测试流水线——这是纯静态 JS:客户端靠 <script> 标签加载,服务端靠平台自带的 min_loadJsFile/require 加载。

client/js/01_SubGame/codes/ 和 server/games 目前都是空的——尚未接入任何具体子游戏。本仓库是平台层 + 可复用框架的脚手架,未来第一个子游戏会在此基础上搭建。

常用命令

没有配置任何构建/lint/测试工具。仓库里唯一的脚本:

# 根据 client/assets/spine/*.json 重新生成 client/generated/spine_assets.js 与 spine_data.js
client/scripts/build_spine_data.cmd        # 内部调用 build_spine_data.ps1

在 client/assets/spine/ 增删 Spine 导出文件后运行;不要手改这两个生成文件。

查看客户端效果需用 HTTP 方式(而非 file://)打开 client/index.html,例如 npx http-server client -p 8080。

shared/(共享算法,待子游戏接入后才有)的改动应按两份文档中「§10 测试纪律」的要求跑 Node 单测验证——目前尚未接入测试框架,意味着需要直接用 node 运行相应脚本。

架构

两套独立代码库,一份线上协议

  • 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/ 的只读同步副本

依赖方向严格单向:01_SubGame/codes → gameabc-framework → gameabc.min.js。框架不反向依赖任何子游戏。index.html 里的加载顺序就是依赖关系图——新增文件必须插在其依赖之后、使用者之前,否则运行时会拿到 undefined(这里没有模块系统)。

可编辑范围:

路径 规则
js/vendor/、js/00_Surface/ 禁止修改
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/ 服务端 shared/ 的只读同步副本;改服务端一侧后再同步

子游戏有两种接入方式(见 docs/client/development-guide/06):内联模式(旧——业务逻辑直接写进三个契约文件)或 Hooks 外置模式(新——三个契约文件退化为纯转发壳,查 SubGameHooks.X 并委托;子游戏逻辑全部放在 codes/SubGameHooks.js + codes/)。每个子游戏二选一,不能混用;Hooks 外置模式的模板位于 gameabc-framework/templates/subgame-entry/。

服务端分层(server/)

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

每个房间的状态挂在 o_room.o_desk.data.* 上(在 export.makewar 内创建,且必须建立 o_room.o_desk ⇄ o_desk.o_room 双向引用)。对局状态禁止用模块级单例/全局变量存放——服务端在同一进程内并发跑多个房间;缓存/定时器/决策表必须以 房间 + seat 为 key(而非仅 seat),并在小局结束/解散/开新局时清理干净。

服务端代替玩家执行的「自动」操作(AI 托管、超时等)必须走与真人操作完全相同的 handler → 广播链路,这样客户端永远不需要为它们单开一套解析分支。

文档(改动前先读)

docs/client/development-guide/与docs/server/development-guide/(按 01→05/04 编号)是本项目遵循的权威、项目专属开发指南——改哪块就读对应编号的文档,而不是从代码反推约定。docs/games/engineering/ 是位于两者之上的、与平台无关的工程方法论层(单一权威数据源 SSOT、单向依赖、职责单一、对扩展开放对修改封闭 OCP、配置优先于硬编码、显式失败优于隐式兜底)。

注意:server/docs/development-guide/ 是 docs/server/development-guide/ 的旧版、部分过期的副本(例如它仍写死可编辑目录为 games2/,而新版已改为「容器目录名不再框架强制」)。两者冲突时以 docs/server/development-guide/ 为准。

两套文档中反复强调的硬性规则(文档记载均对应过真实事故):

  • 全面严格 ES5(var/function,继承用 Object.create;禁止 let/const/箭头函数/模板字符串/class/解构/Promise)。
  • 服务端 require 只能写在文件开头的守卫块内(if (typeof require !== 'undefined') { var X = require(...); })——禁止在函数体内中途 require;两个运行时之后统一按同一个全局名引用。
  • 成败判定只看推送包上的 data.success——绝不用 status/code,也不能写两者都判的兼容兜底。
  • 对影响发牌/庄家/手牌/计分/重连的权威数据,禁止用默认值掩盖缺失(|| 0、|| []、|| '')——应直接显式报错;默认值仅允许用于纯展示/日志字段。
  • 一个职能只在一个模块实现——需要该能力时调用其所属模块,而不是在别处重新实现一份。

测试纪律

与 docs/client/development-guide/05 §10、docs/server/development-guide/04 §10 一致,是本项目对测试代码的硬性要求:

  • 测试代码可以不守严格 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 等破坏性操作。