Commit Graph
100 Commits
Author SHA1 Message Date
joywayerandClaude Opus 5 cfa0409d94 二七王:CLAUDE.md 补记断言反向验证与 CRLF 脚本改写注意事项
两条都是本轮开发中真实踩到的:
- 新增守卫断言若不做「故意破坏→看是否变红」,可能加的是空转断言;
- 仓库为 CRLF 行尾,脚本化改写用 `\n` 匹配会一行不改却静默成功,
  曾据此差点得出「测试抓不住原缺陷」的错误结论。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:07:15 +08:00
joywayerandClaude Opus 5 af779f473a 二七王:C-1 修复 room.mySeat 永远为 -1
平台 12_Logic.js 在第 389 行调 Game_Modify.appStart(),而 C_Player = new Player(-1)
要到同函数第 480 行才执行,此刻 typeof C_Player === 'undefined',appStart 整体空转;
之后玩家登录 SetSeat 拿到真座位,appStart 再也不会被调用。于是 mySeat 永远停在 -1:
发包带 seat:-1 被服务端 check_player 全部拒绝、StartWar 差异化下发认领不到本座位而
整包丢弃、SeatMap.toDisplay(-1) 抛错——且全程无一处报错。

- 新增 SubGameHooks._syncMySeat():room.mySeat 的唯一写入处(SSOT),幂等、
  座位非法或 C_Player 缺席时保持原值不倒退
- 在 setRoomDes / StartWar / Reconnect / ReconnectNoMakewar / appStart 各调用一次,
  不赌「所有路径都经过某一个入口」
- 新增 SubGameHooks.changeSeat 并同步座位:Desk.change_seat 是唯一「座位中途变化
  且不重走 setRoomDes」的路径,漏掉会让 mySeat 静默过期
- Rpc._seat() 改 fail-fast:座位非 0/1/2 即抛错,不再无声透传 -1
- 新增 client/tests/test_appstart_timing.js:复刻平台真实初始化时序,
  全程不直接赋值 mySeat;已反向验证(退回 6 个调用点后核心断言转红)
- test_rpc_send 补座位非法抛错断言;test_consistency 删除空转的 my.mustCard EXCLUDE

client 23 脚本全绿,server 17 脚本 / 680 checks 全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 21:05:46 +08:00
joywayerandClaude Opus 5 22e43cf2e6 二七王:平台接线与加载段
SubGameHooks 只解包转交、不写业务:_ReceiveData 收到的是完整包,
按 rpc 分发;Free 的参数是平台已取出的 deskfree、可能为 null;
appStart 只记座位与房间选项,UI 初始化留给 C 阶段。

index.html 里 handler 排在 Dispatcher 之前——后者注册时要引用全部 handler。

另加 test_index_load_order.js:按 index.html 实际 script 顺序自动加载
codes/ 全部文件 + 3 个框架依赖,验证顺序无遗漏、无环,并断言
EQW_Dispatcher 路由表确已注册,作为长期守卫。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:16:45 +08:00
joywayerandClaude Opus 5 dac4a94b38 二七王:夹具补甩牌样本,重连恢复 baozhu
导出脚本原来全出单张,单张一手无所谓顺序,服务端两个顺序口径的分岔
在夹具里永远测不出来。改出牌策略(种子不变):首家优先合法甩牌 → 领
最大的一对 → 最小单张;跟家凑合法跟牌;提交时刻意反转成升序,模拟真实
客户端的点击顺序,把「下发顺序是否被请求顺序污染」暴露出来。
中段 step5 快照锚点从 1 个加到 5 个——开局那三张快照的 pushlist 还是空的,
只有打到一半的快照才真的比对到出牌历史。

重导结果:51 手出牌,一手多张 30 手、合法甩牌 4 手,deskinfo 快照 18 张,
仍覆盖 step 1/2/3/5/6;连跑两次逐字节相同。
该样本在修复前使 test_consistency 5 个比对点转红,修复后全绿。

前端 ResyncHandler 补上 PushCards.baozhu 的映射(服务端已补发),
test_resync 加正反两条断言。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:12:24 +08:00
joywayerandClaude Opus 5 4508fe005f 二七王:出牌顺序收敛为唯一口径,重连包补发 baozhu
同一份数据「这一手打出的牌」此前有两个顺序口径:chupai*/playproc.cards
回显客户端请求原序,重连包 pushlist 则从 playround 反查后自己再排一次序。
一手多张(甩牌/对子/拖拉机)时两者必然分岔;同编码的一对牌更是排序都
区分不了,靠"两处各自排序"永远收敛不了。

改为单一写入处:
- 新增 order_playcards(),do_playcard 入口归一化一次(顺带隔离
  can_playcard 的原地排序,不再就地改写 pack.data.cards)
- 新增 paiju.playhistory,出牌当场归档;新增 get_pushlist() 只做深拷贝
- class.export.js 删掉整段反查+重排,改为直读归档
- do_playcard 三个分支都给 re.cards,mod.js 去掉回落到入参的兜底

顺带修复重连包漏发 baozhu:它是余主公示与明牌按钮的开关,此前只在
chupai 包里给,报无主后重连按钮会凭空消失。现与 chupai 同源同门控下发。

协议文档新增 §0.6「一手出牌」的顺序口径,并补 PushCards.baozhu 字段。
测试:pushlist 一节改为真实链路驱动(一手多张 + 乱序提交),断言升级为
「重连每一手与当时的 chupai.cards 逐元素相等」;三处修复均已退回验证转红。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 20:12:03 +08:00
joywayerandClaude Opus 5 5689ceab6f 二七王:重新生成夹具、BuryHandler 接住 maipai.playproc,修正甩错断言的错误映射
配合上一个 commit 服务端补发的 maipai.playproc,重新生成真包夹具(diff 只新增
三家 maipai 包各一份 round-1 初值 playproc,无其他变化)后跑一致性测试,暴露出
前端还有一处对应缺口:BuryHandler.js 组 table.playproc 时没有接 data.playproc
(PlayHandler.js 早已对 chupai1/2/3 做了同样的原样拷贝),于是「埋牌完成→庄家
首出」窗口增量路径的 table.playproc 停在上一阶段的 null。现补上,并在
test_handlers_bury.js 新增一条用例守住、用「退回修复必转红」反向验证。

test_handlers_play.js:84 的 `S.table.playproc.shuaicuo` 断言的正是此前 Fix Round 2
裁决②(本手信息收进事件载荷、不进 GameState)判定为缺陷的旧映射,playproc 改为
原样拷贝服务端结构后必然读 null 硬崩。按 task-12d-report.md §8 的裁决与建议替换:
断言改为守事件载荷(EQW_CARD_PLAYED 的 shuaicuo/order/seat/cards/count/flower/
cardtype)+ playproc 原样拷贝,强度不降——原断言只守住"甩错标志被处理"这一件事,
新断言额外多守了"本手信息不得混入 playproc"一条。

client/tests/test_consistency.js 全绿(23 checks,此前 20 绿/3 红);
client/tests/run.js 全绿(20 脚本);server/games/erqiwang/test/run.js 全绿
(17 脚本/675 checks,见上一个 commit)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 19:48:54 +08:00
joywayerandClaude Opus 5 cdf2bc730e 二七王:maipai 补发 playproc(增量路径与重连路径对齐)
前端一致性测试剩余 3 红的根因:do_burycard() 内部已调 new_playround 初始化好
round-1 的进行态,但 mod.js 组 maipai 包时没有放 playproc(协议 §9 字段表也没有),
「埋牌完成 → 庄家首出」这段窗口增量路径 table.playproc 为 null,重连路径却已有初值——
与上一个 commit(8452535)给 maipai 补 seatlist 是同一个窗口、同一条理由,被漏在了同一处。

修法:复用既有的 get_playproc() 快照函数(与 chupai1/2/3、PushCards.playproc 同源,
本身就是逐字段深拷贝,不引入活引用风险)。可见性:此刻一张牌未出,round/start/currseat
等全部由桌面公开信息推出,cards 全空、shuai_demand 为 null,不含私密信息,故与
chupai1/2/3.playproc 一样三家整体下发、不受查牌模式门控。

协议文档 §9 同步补字段说明。test_rpc.js 新增 5 checks:庄闲同值、与重连快照比对、
round-1 初值断言、不查牌模式下仍恒有、深拷贝守卫;已用「退回修复必转红」反向验证
(3/5 转红)。既有 121 checks 未改一条断言,全绿(17 脚本 / 675 checks)。未涉及 shared/。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 19:46:32 +08:00
joywayerandClaude Opus 5 729128aa9d 二七王:前端 handler 六处下发面漏读/错读修复(增量路径对齐重连路径)
一致性测试(同一局真包:逐包增量回放 vs 一次性 deskinfo 重连)暴露的六处前端缺陷:

① PlayHandler:table.playproc 由「字段映射错误」改为原样拷贝
   原实现把这一手的 seat/cards/count/flower/cardtype 灌进 playproc,而服务端 playproc
   是本轮进行态(round/start/currseat/.../cards),两者几乎无交集、撞名的 cards 语义还相反。
   服务端已在 chupai1/2/3 补发 playproc,前端零推导直接镜像,不自行累积 round/maxseat。

② 本手信息改走 EQW_CARD_PLAYED 事件载荷,不进 GameState
   {order, seat, cards, count?, flower?, cardtype?, shuai?, shuaicuo?},带 ? 的缺字段不兜底。
   落牌是一次性表现而非对局状态;新立 GameState 字段只会再制造一处增量/全量不一致。

③ PlayHandler:table.pushlist 逐手落盘、按轮归档(GameState.pushPlay)
   chupai1 开新的一轮、2/3 落进当前轮,任意时刻都与 deskinfo 的全量重建相等(服务端
   pushlist 含进行中的当前轮)。不读 chupai3.playproc.cards——那是下一轮的进行态(协议 §13)。
   仅可查牌模式累积,与服务端同一道门控,否则不查牌房会凭空攒出重连没有的历史。

④ ResultHandler:归档收尾轮(最后一手走 jiesuan.chupai,不发 chupai3)
   夹具实证 chupai1×28 / chupai2×28 / chupai3×27。jiesuan.chupai 只有 seat/cards/maxseat。
   归档随即被结算收敛清空(deskinfo step6 不带 PushCards),但 emit 同步,落牌表现在清空前取值。

⑤ BuryHandler:接住 maipai 新增的 seatlist(此刻为全初值,界面无差别但重连侧有)

⑥ ResyncHandler 的 ChooseMain/BuryCards 补映射 curmultiple(原重连重建为 0,抓分角标掉档);
   BuryHandler 埋牌完成后清空 my.bottomCards——底牌上庄时已并入手牌,埋牌后只剩 burycards
   有意义,deskinfo 的 PushCards 不下发 bottomcards,重连重建恒为 [](语义已逐条核实)。

一致性测试 8 红 → 3 红。剩余 3 条同源:服务端 maipai 漏发 playproc(与本次服务端补发
seatlist 是同一个窗口、同一个理由),前端无法自补且不得伪造,详见报告 §7 待裁决。
另有 test_handlers_play.js:84 断言的正是 ① 修掉的错误映射,按纪律未改测试,见报告 §8。

未触碰 server/、client/tests/(含夹具与 EXCLUDE);服务端 29 checks 全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 19:41:48 +08:00
joywayerandClaude Opus 5 f7b87f32e8 二七王:重新生成真包夹具、补捕 StartWar、修复一致性测试用例间污染
服务端 8452535 改了下发面(chupai1/2/3 新增 playproc、deskinfo step2/3 新增
curmultiple、maipai 新增 seatlist、bottomcards/burycards 顺序统一),前端真包
夹具 packets.json 已过期,重新导出并逐项核对新字段存在与三处/两处顺序一致。

夹具新增顶层 startwar[](每座位一份,结构=deskwar=deskinfo,取自 do_new_paiju
之后、jiaofen 之前的同一时刻),独立存放、不混入 seats[],避免 packetIndex 对
齐锚点错位;test_fixture.js 补相应断言。

修复 test_consistency.js 的用例间污染:EQW_GameState.reset() 按设计不清
room.playerScores(跨局累积量),旧版增量路径从不写它,实际靠"沿用同进程里
上一次全量路径写入的残留"凑出假绿,换座位顺序或调整执行顺序就会转红。改为
每个对齐点独立起步(buildIncremental/buildFull 各自 reset),且增量路径先喂
StartWar 再喂增量包,与真实客户端时序一致,不再依赖任何隐式残留。已验证正
序/倒序跑出的 PASS/FAIL 集合逐条相同。

修完后 23 checks:15 绿、8 红。红项均已归因:3 处为任务已知的前端未接线缺口
(table.playproc 映射、table.pushlist 累积、maipai.seatlist 消费),另发现两
类同类缺口(ResyncHandler 未映射 deskinfo 新增的 curmultiple 字段、BuryHandler
埋牌后未清空 my.bottomCards),均未改动 client/js 正式代码,留给下一个前端任务。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 19:28:13 +08:00
joywayerandClaude Opus 5 84525359fc 二七王:服务端下发面四处缺陷修复(增量路径与重连路径对齐)
前端一致性测试(同一局真包:逐包增量回放 vs 一次性 deskinfo 重连)暴露的四处
服务端缺陷,均属「同一份数据两条路径给的不一样 / 有一条根本没给」:

① chupai1/2/3 补发 playproc(本轮进行态)
   与 deskinfo.PushCards 共用新增的 get_playproc() 快照函数,结构取值完全一致。
   playproc 是全程复用的活对象(new_playround 就地重置),故快照做逐字段深拷贝,
   避免已发出的包被后续出牌回改。可见性:全部字段由桌面公开信息推出,
   与既有 PushCards.playproc 一样三家无差别下发。

② deskinfo ChooseMain(step2)/BuryCards(step3) 补发 curmultiple
   与 shangzhuang 推送同源同值;此刻一张牌未出、捡分恒 0,必为 +3(大光)。
   原先重连重建为 0,顶部「抓分」角标掉档。

③ maipai 补发 seatlist
   与 chupai/PushCards 同一张表、同一道查牌模式门控;埋牌完成时为初始化空表。

④ bottomcards / burycards 收敛到单一权威源(SSOT)
   - burycards:maipai 原样回显了客户端请求包里 cards 的顺序,改取 get_burycard()
     权威快照,与 PushCards.burycards 同一函数。请求包只承载「意图」,其顺序不可信。
   - bottomcards:get_bottomcards 按「调用时的 flower」排序,导致上庄推送(flower=-1)
     与重连 BuryCards(flower 已定) 两种顺序。底牌是选主【之前】就翻给玩家看的、
     发牌结束即固定的快照,故改为发牌时算一次并冻结,get_bottomcards 返回其副本
     (order_cards 是原地排序,交出本体会被调用方就地重排)。三个调用点无需改动。

协议文档同步 7 处(新增 playproc/seatlist/curmultiple 字段说明与两处排序口径)。
新增 test_rpc.js「下发面一致性」用例组 21 checks,含 28 轮真实牌局逐包逐座位比对
(249 次、0 不一致)与深拷贝守卫;每条均已用「退回修复必转红」反向验证。
既有 649 checks 未改一条断言,全绿(17 脚本 / 670 checks)。未涉及 shared/。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 19:17:09 +08:00
joywayerandClaude Opus 5 c4c061b0e1 二七王:修复三处前端漏读(Task 12 一致性测试 Fix Round 1 · 类别 A)
一致性测试发现的 8 处不一致中,分诊出 3 处确属"deskinfo 里有、增量 handler
没读"的前端缺陷,本次修复:

- room.playerScores:reset() 不再清空,改为跨局保留字段;ResultHandler 从
  jiesuan 的 aset.seatlist[i].score(协议 §14「累积得分」)回填,避免一局
  中段(fapai 后至 jiesuan 前)总积分持续为空。
- call.currcall/call.calls:CallHandler.handleShangzhuang(step1→2 转场)
  清回默认值——deskinfo 从 ChooseMain 起就不再带 CallRun,这两个叫分过程量
  该阶段起无意义。
- ResultHandler._applyResult(jiesuan/解散共用)补齐结算清场:把 aset.*/
  turn.*/call.*/table.*/my.bottomCards/buryCards/cards 清回 reset() 默认值,
  与 deskinfo step6 只应用 Balance 分组时的重建结果对齐;顺带清掉出完最后一张
  牌的座位因 jiesuan.chupai 不带 cardsinhand 留下的幽灵手牌。

发现 3(curmultiple)、发现 6(seatlist 窗口期)判给服务端,发现 4/5
(table.playproc/pushlist)待裁决,均未改动。test_consistency.js 由 9
PASS/14 FAIL 转为 14 PASS/9 FAIL,EXCLUDE 白名单未改一字。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:57:54 +08:00
joywayerandClaude Opus 5 33c18a2c17 二七王:增量 vs 全量一致性测试(B 阶段核心验收)
把前端红线「视图 = f(服务端快照)」变成可执行断言:按序喂推送包累积出的
GameState,必须与同一时点 deskinfo 全量重建的完全相等。

不一致只有两种可能,都要修:前端私存了服务端不知道的状态,或服务端漏发了字段。
EXCLUDE 白名单只收「一次性事件」与「重连不重放」两类,每条都写明理由。

本测试当前保持失败:比对发现了 7 处前端 handler 缺陷(含 table.playproc 字段
结构与协议不符等高优先级问题)与 1 处疑似服务端下发顺序不一致,均未在本次
改动中修复,详见 task-12-report.md,留待裁决。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:48:08 +08:00
joywayerandClaude Opus 5 e2d470d37f 二七王:修复夹具导出的活引用污染并引入固定种子
审查发现 C-1(阻塞):export_packets.js 的 snap() 直接存了
class.export.js get_deskinfo() 返回的 PushCards.playproc/seatlist——这两个
字段在服务端是无拷贝的活引用赋值,脚本却在整局跑完后才统一 JSON.stringify
写盘,导致中途出牌不断原地改写这两个对象,已存的 step5 快照被事后篡改成终
局状态。修法是在 snap() 里对 get_deskinfo 的返回值套一层已有的 clone()。这
是导出脚本"延后序列化"这个用法本身的缺陷,不动 server/。

同时按裁决引入固定种子:min_random 只在 class.paiju.js 的发牌洗牌里用到,
原始实现走 Math.random();改为在导出脚本进程内 monkey-patch 一个 xorshift32
确定性 PRNG(写法与服务端既有测试 test_flow.js 等一致),种子值 DEAL_SEED 写
成具名常量。连跑两次导出脚本,packets.json 逐字节无 diff。

test_fixture.js 补强三条断言:底牌"只发给部分座位"改为精确断言恰好1个座位;
新增 packetIndex/roomtype/step 自洽性检查;step5 快照的 playproc 回归锁直接
捕获本次的活引用污染类缺陷;deskinfo 阶段覆盖从"≥3个"改为显式钉死
[1,2,3,5,6]。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:35:02 +08:00
joywayerandClaude Opus 5 e61725da40 二七王:前端测试的服务端真包夹具
用服务端 test/_rpc.js + class.desk.js 脚手架跑一局(发牌→叫分→上庄→选主→
埋牌→28轮出牌→结算),把真实下发包按座位导出成 JSON 供前端回放。不手写假
包——测的是真实契约,协议文档写错或前端理解偏差都会在这里暴露。

_rpc.js 的 setup() 只接受已构造好的 o_paiju,不走 class.desk.js,产不出
fapai;改用 test_flow.js/test_endgame.js 的 mkRoom() 模式(D.new + 全局
youle_erqiwang.app/import 与 mod.app/mod.import 同指一个 sent 数组),两条
既有用法缺一不可。

夹具自检守住质量:覆盖 fapai/jiaofen/shangzhuang/xuanzhu/maipai/
chupai1/2/3/jiesuan 全部阶段、保留座位差异(底牌只发给部分座位)、每个下发
包都带 success。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:22:51 +08:00
joywayerandClaude Opus 5 a5ca3bc5d9 二七王:重连与开局 handler,路由表注册齐全
重连即重画:断线重连与硬刷新复用同一条路径(填 GameState → emit RESYNC_ALL),
不为重连单写一套渲染。deskinfo 按 step 分组填充,重连不重放 70 分开底那类
一次性事件。StartWar 支持差异化下发,按本座位取自己那份。

路由表 12 条注册齐全,test_dispatcher 的完整性断言随之转绿——
漏接一个包就会红。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:15:04 +08:00
joywayerandClaude Opus 5 e8fc3a9bfa 二七王:结算与解散 handler
三种结算来源组成不同(正常出牌 / 投降 / 解散),共用一套落地逻辑:
有哪组写哪组,缺的保持 null。

解散走平台的 Game_Modify.Free 入口而非收包分发表——它的 route 是 room,
被平台分流走了。取值路径因此比协议文档少一层,且参数可能为 null
(首局发牌前解散),已覆盖。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:09:06 +08:00
joywayerandClaude Opus 5 070ff37db1 二七王:明牌、提示、准备 handler
明牌数据进 GameState(面板可反复开关查看);提示不进——它是一次性通知、
不是对局状态,重连也不重放,存下来只会变成幽灵数据,故只随事件带出。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:04:33 +08:00
joywayerandClaude Opus 5 47c8a27d0d 二七王:补全出牌 handler 测试里 chupai3 各用例的 turn.seat 断言
三条 chupai3 用例(不带 grade 那条、跨包累加的两条)之前都带了 nextseat
字段却没验证它,读起来像遗漏,也让"chupai3 同样会推进控制权"这件事只被
验证了一次。补齐后四条 chupai3 用例均验证 turn.seat;跨包累加的两条从
turn.seat 由 0 变 1,顺带证明了第二个包确实被处理,不只是看 grade 一个数字。

只改测试文件,PlayHandler.js 未改动。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 18:01:56 +08:00
joywayerandClaude Opus 5 5a77e4b132 二七王:修正出牌 handler 注释并补强 grade 累加与 nextseat 测试
审查发现简报本身两处缺陷:
1. grade 累加测试断言力不足,无法区分"累加"与"仅在字段存在时赋值"两种
   实现——补一条跨包连续收两个 grade 的用例(10 再 15,断言最终 25)钉住
   累加语义。
2. chupai3 实际也带 nextseat(服务端在 switch 前统一设置三个包),简报
   注释误写成"chupai1/2 独有";修正文件头注释,并给 chupai3 测试用例补
   上 nextseat 与对应的 turn.seat 断言,使包结构与真实下发一致。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:58:42 +08:00
joywayerandClaude Opus 5 5637d63c3b 二七王:出牌 handler
三个包字段不同:chupai1 带牌型信息、chupai1/2 带 nextseat 与 mustcard,
chupai3 带 maxseat 与本轮得分。

两个易错点已覆盖:cardsinhand 只有出牌者自己有,别人出牌时缺该字段不能抹掉
自己的手牌;mustcard 只对当前轮有效,没给就要清空,否则上一轮的建议会残留。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:51:36 +08:00
joywayerandClaude Opus 5 daf2708af9 二七王:选主与埋牌 handler
选主后主牌集合变化、手牌需重排,但重排是渲染时由 core/CardOrder 现算,
不在状态里落地——GameState 只镜像不派生。

埋牌区分 burycards(埋牌底牌)与 bottomcards(底牌),两批不同的牌;
闲家无 cards/burycards 但可能有 liangpai,缺字段一律不抹已有值。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:46:01 +08:00
joywayerandClaude Opus 5 081130b020 二七王:发牌与叫分 handler
fapai 是一局起点,先 reset 清上局残留(保留 mySeat 与 options)。
叫分里 0(不叫)与 null(还没叫)严格区分。上庄的 bottomcards/cards 只有庄家有,
缺字段时保持原值、不抹掉闲家已有手牌——GameState 的「缺字段不兜底」在此体现。

新增 GameState._apply 助手:按 {目标键:源键} 映射写入,源键不存在就不写。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:41:09 +08:00
joywayerandClaude Opus 5 01eb6c60b5 二七王:收包分发器与失败回包处理
分发器只分发不写业务。判定顺序上先看 data.success,失败包一律先分流给
FailHandler——协议规定失败回包的 rpc 与请求同名(chupai 失败回 chupai,
成功走 chupai1/2/3),不先分流则每个业务 handler 都要自己判一遍。

未知 rpc 只警告不抛错,畸形入参不崩。路由表完整性断言留待全部 handler 就位。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:36:15 +08:00
joywayerandClaude Opus 5 65674542b3 二七王:补全 FORBIDDEN 列表覆盖完整协议字段
FORBIDDEN 列表扩充到覆盖协议中服务端→客户端的全部字段名,
作为回归网防止未来在发包方法中意外泄漏服务端计算结果。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:32:44 +08:00
joywayerandClaude Opus 5 ff46d874c0 二七王:前端语义化发包
route 显式写 erqiwang——RpcHelper.sendGameRpc 预设 route="room",
用它包会被路由到平台房间模块、到不了子游戏 mod.js 且不报错。

只做形状校验(牌 id 范围/去重/张数),规则合法性由服务端裁定;
不合形状即抛错、不发残缺包。测试断言发包字段集合不含任何结论字段。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:28:11 +08:00
joywayerandClaude Opus 5 a82969a23b 二七王:前端 roomtype 位串解析
与服务端 class.config.js 的 parse() 同规则;缺失/非字符串/过短一律按 '0',
这是协议 §0.5 明文规定的行为。已逐位对照服务端确认。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:21:39 +08:00
joywayerandClaude Opus 5 4e79f135ce 二七王:前端事件常量与 GameState 骨架
GameState 是服务端快照的镜像、前端 SSOT:只镜像不派生、字段名对齐协议、
缺字段不兜底。reset 清对局态但保留 mySeat 与 options(跨局不变)。
事件只带「发生了什么」,数据由订阅者从 GameState 读,避免同一份数据两处存。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:19:43 +08:00
joywayerandClaude Opus 5 2df7179266 二七王:前端子项目 B 实施计划
13 个任务,每个自带 TDD 循环与提交:GameState 骨架与事件契约、roomtype 解析、
语义化发包、分发器与失败回包、五组 handler、重连与开局、服务端真包夹具、
增量 vs 全量一致性、平台接线。

自检修掉两处:Task 8 一条拿自己跟自己比的恒真断言;Task 12 引用的
fx.roomtype / snapPoint.packetIndex 在 Task 11 的夹具结构里没定义
——packetIndex 是两条路径的对齐锚点,缺了一致性测试无从比对。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:16:11 +08:00
joywayerandClaude Opus 5 d4de34c951 二七王:B 设计修正发包路由与解散入口
两处对着平台代码核实后修正:

发包必须用 RpcHelper.sendRpc('youle','erqiwang',...),不能用 sendGameRpc
——后者预设 route="room",包会被路由到平台房间模块、永远到不了子游戏 mod.js,且不报错。

解散结算根本进不了 _ReceiveData:平台按 route 分流,room 路由交给 Net[rpc],
只有子游戏路由才转 _ReceiveData。平台另有专门入口 Game_Modify.Free(Desk.deskfree),
取值路径因此比协议文档少一层(deskfree.data.aset),且参数可能为 null 需容忍。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:05:11 +08:00
joywayerandClaude Opus 5 f63de40229 二七王:前端子项目 B(网络与数据镜像)设计定案
架构:点击 → 语义化发包 → RpcHelper;推送 → 唯一分发入口 → 一 rpc 一 handler
→ 写 GameState → emit 语义事件。handler 不引用任何 UI 组件,故 B 可脱离 C 独立完成与测试。

GameState 只镜像不派生、字段名对齐协议、缺字段不兜底。事件只带「发生了什么」,
数据由订阅者从 GameState 读,避免同一份数据两处存。

两条核心验收把红线变成断言:增量累积与 deskinfo 全量重建必须完全相等
(即「视图 = f(服务端快照)」);发包字段集合不含任何结论字段。
测试数据用服务端 _rpc.js 脚手架捕获的真实下发包,不手写假包。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 17:02:40 +08:00
joywayerandClaude Opus 5 8dba099717 二七王:HIST_FAN/MING_FAN maxWidth 由 490 改判定为 480,让 spacingMin 真正生效
上一版把 maxWidth 收窄到 490 只是让 RIGHT 座位恰好贴住画布边界(零余量),且
spacingMin:-96 在 28 张时永远不触底、成了死参数。裁定改为 480:
(480-110)/27-110≈-96.30 超过 spacingMin,spacing 被截断为 -96,总宽变为
28×110+27×(-96)=488,与清单原注释「每张露14px、总宽488」精确对齐——500 是笔误,
488 才是当初想要的值。

求解器验证(count=28):LEFT 16→504、RIGHT 791→1279、SELF 396→884,三座位均落在
0-1280 内且有余量。同步订正 Layout_Result.js 注释与清单 §6.9c 文字/数值。

client/tests/run.js 与 server/games/erqiwang/test/run.js 均全绿,精灵总数(380)/
布局节点数(83) 未变。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 09:08:48 +08:00
joywayerandClaude Opus 5 2b1dd0d947 二七王:修复出牌操作条/亮牌历史布局越界,补主牌统计条改名遗漏
- I1:Layers.js 群组 206 改名遗漏一处注释(亮牌信息条 → 主牌统计条),全仓复查确认无第二处
- I2:出牌操作条补「提示」项后 anchor:center 导致已有两项挤偏,改用 anchor:left 并反推
  anchorX=446,令「出牌」按钮回到参考图实测 x≈556(求解器验证:446/556/748)
- I3:HIST_FAN/MING_FAN 的 maxWidth 反推算错(旧注释「488」有误,实际总宽恒等于
  maxWidth=500),导致 RIGHT 座位整排超出画布 5px;收窄为 490 使三座位求解结果落在
  0-1280 内,订正注释与清单 §6.9c 对应文字
- M3:同步修正计划文档里遗留的旧名「亮牌信息条/亮牌条」
- M6:test_constants.js 补 items[].key 的三命名空间解析守卫(此前完全无守卫)
- M5/M7:订正 ACC_ROW_TEXT_GRID 与扣底按钮宽度的估值依据说明
- M1/M2:清单 §6.9 子节重新排序为 6.9a→6.9b→6.9c 并统一标题层级,修正脚注星号误渲染

client/tests/run.js 与 server/games/erqiwang/test/run.js 均全绿,精灵总数(380)/
布局节点数(83) 两个钉死断言未变。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 09:06:01 +08:00
joywayerandClaude Opus 5 8f36b61620 二七王:补齐三处配置缺口(扣底按钮 / 出牌提示按钮槽位 / 三个 View 的布局节点)
这三处都是「精灵或按钮已在清单里定案,但配置层缺东西」,不补的话 C/D/E 阶段
写渲染代码时会发现没有布局节点,极易就地硬编码坐标(违反零裸值红线)。

1) 底栏「扣底」按钮没有精灵(清单 §2.6 列了 5 个功能按钮,只登记了 4 个)
   - 新增 BTN_BURY_CARDS(1056) / BTN_BURY_CARDS_TEXT(1057),群组 205 号段内空闲号,
     资源沿用 BTN_BOTTOM_FUNC,与其余四个按钮同构。
   - FOOTER_FUNC_BUTTONS 的 items 由 4 项扩到 5 项,扣底紧邻底牌(照 §2.6 表序)。
     扣底宽 78 是【估值】:清单未给,取同为两字按钮的「底牌」实测值,已在配置与清单里注明。
   - 扣底看【埋牌底牌】(burycards)、底牌看【底牌】(bottomcards),两批不同的牌,
     JSDoc 里补了防写反的提示(对应验收清单第 13 条)。

2) 出牌阶段「提示」按钮有精灵、无布局槽位
   - PLAY_OPERATION_BAR.items 补上 PLAY_BTN_TIP,顺序照清单 §1.7 行文:倒计时 / 出牌 / 提示。
   - 宽 180 是【估值】:清单 §6.8 自身漏了该项;与埋牌条的提示按钮同资源 BTN_TIP
     (§3.3 实测 180×63),埋牌条 items 首项亦取 180,故沿用。
   - 顺带记下一处待裁定项:本条 itemHeight 为 55(对齐 BTN_PLAY 157×55),
     而 BTN_TIP 原生高 63,高度差待设计稿定。

3) AccountView / HistoryView / MingPaiView 一个布局节点都没有
   - AccountView(§6.9a 新增):外壳 6 个节点 + 玩家栏「容器 ACC_ROW_CONTAINER +
     行节点 ACC_ROW(itemHeight 即行高,行数由 ctx.count 给出)+ 行内模板贴附到行矩形」
     + 面板外 4 按钮,共 14 个节点。行内节点求出的是相对容器的坐标,可直接喂
     SpriteCopyUtils.create(与 §5.6a 冲关牌同一用法)。
   - HistoryView / MingPaiView(§6.9c 新增):遮罩 + 容器 + 每家一排牌(fan) + 座位标签,
     各 4 个节点。照 CHONGGUAN_FAN 同构、锚点沿用 §6.7 冲关牌的三家落点;
     差别只在张数上限——出牌历史摊平后可达 28 张以上,故 maxWidth 放宽到 500、
     spacingMin 收到 -96【估值】。MING_* 只有 LEFT/RIGHT 两个 bySeat 变体(明牌不看自己)。
   - 坐标来源:大局结算.png / 冲关牌型显示.png 目视实测,w/h 取资源尺寸;
     配置与清单里均已注明「目视实测估值,待设计稿复核」。
   - 运行时才确定的字段(动态文字宽高、随行浮动的 anchorY、数字精灵宽)一律
     runtime 显式声明。

钉死断言按实际重新数并更新:精灵总数 378 → 380,布局节点总数 61 → 83。
另把 ACC_TPL_SCORE_NUM 加进 NUM_STYLE 同源校验表(与 RESULT_SCORE_NUM 同一套资源)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 08:40:48 +08:00
joywayerandClaude Opus 5 f982460c9c 二七王:群组 206 改名为主牌统计条,与 design §8.2 的「亮牌」消歧义
群组 206 原名「亮牌条 / LIANGPAI_BAR」,但它的实际用途与坐标 (35,400) 对应的是
清单 §2.5「自己的主牌统计条」——手牌上方显示 x对 x主、前端本地统计、有手牌时常显。
而 design §8.2 真正的「亮牌」是庄家埋牌后向两个闲家亮出自己全部固定主牌的具体牌面,
走遮罩面板(群组 242,复用 HIST_* 精灵)。清单 §0.0 专门用一节区分「亮牌 / 余主公示 /
明牌 / 开底」四类公开信息,名字撞车极易把渲染写到错误的界面上。

统一改名(ID 一律不变,只改名字):
- 群组   EQW_Groups.LIANGPAI_BAR   → ZHU_STAT_BAR (206)
- View   EQW_Sprites.LiangPaiView  → ZhuStatView,容器 LiangPaiBar → ZhuStatBar
- 精灵   LIANGPAI_BG/_TEXT (1030/1031) → ZHU_STAT_BG / ZHU_STAT_TEXT
- 布局   EQW_Layout.LIANGPAI_BG    → ZHU_STAT_BG
- 资源   EQW_Images.BAR_LIANGPAI   → BAR_ZHU_STAT (605)

同步清单 §0.0 / §0.3 群组表与号段表 / §1.6 / §2.5 / §3.5 / §3.6 / §5.1 / §5.7b /
§6.5 / §7.2 T-9 / §7.3 第 5 条,并在 §0.3 群组表下加修订说明(2026-08-27 + 缘由)。

一并订正清单 §1.6 的一处笔误:收到 liangpai 时应弹出亮牌遮罩面板(群组 242),
原文写的是「渲染 §2.5 亮牌信息条」——正是同名异实造成的串位。
EQW_Anim.LIANGPAI_AUTO_CLOSE 属真正的亮牌面板,保持原名,仅补注释说明归属。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 08:40:09 +08:00
joywayerandClaude Opus 5 dc08513886 gitignore 忽略 .superpowers 工作区
多智能体执行过程的临时产物(任务简报、审查包、报告),不应入库。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 08:23:56 +08:00
joywayerandClaude Opus 5 92a8caa53f 恢复 githooks 机械红线校验
2026-08-11「先关闭 githooks」注释掉了唯一的 exec 行,提交闸此后一直空转。
恢复前已空跑验证:暂存一个含现代语法的测试文件与一个 config 文件,
退出码 0——测试豁免生效、存量代码不被误拦。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 08:23:44 +08:00
joywayerandClaude Opus 5 b11a3f17a9 二七王:Sprites_CreateRoom.js 补 CreateRoomView 的布局说明注释
分区标题标出 Layer(平台 27)与 Group 250,正文含 ASCII 版式图:类别竖排 ×
选项槽横排的两层嵌套,标出三个类别标题、8 组「选择框 + 文字」与房卡附注各自
对应的精灵常量与 attach 目标;另附帧号公式、roomtype 五位映射、房卡联动表、
资源说明与 @see 出处,并注明参考图是别的游戏、只借版式不借内容。

纯注释改动,数据部分一字未动(精灵总数仍 378、布局节点仍 61)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 01:52:54 +08:00
joywayerandClaude Opus 5 6c339060bf 二七王:Sprites_Result.js 补 5 个 View 的布局说明注释
为 AsetResultView / ChongGuanOverlayView / AccountView / HistoryView /
MingPaiView 各补一块 JSDoc:分区标题标出 Layer 与 Group,正文含 ASCII 版式图
(小局结算三家光晕与底牌算式区、冲关叠加层三家牌位与遮罩、大局结算面板的
标题栏/玩家栏/规则栏/面板外按钮条、出牌历史与明牌的三排/两排版式),
另附元素说明、关闭方式对照、资源说明、精灵 ID 分配与 @see 出处。

纯注释改动,数据部分一字未动(精灵总数仍 378、布局节点仍 61)。
如实记下三处待补:判定结果动画 D-8 无设计稿;AccountView / HistoryView /
MingPaiView 尚无 EQW_Layout 布局节点,图中坐标为参考图目视实测。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 01:51:39 +08:00
joywayerandClaude Opus 5 1644b3ebb2 二七王:Sprites_Action.js 补 5 个 View 的布局说明注释
为 CallPanelView / ChooseMainView / OperationBarView / CountdownView /
OverlayView 各补一块 JSDoc:分区标题标出 Layer 与 Group,正文含 ASCII 版式图
(叫分面板 7×2 档位阵与单档按钮三精灵拆解、选主面板 4 花色+投降一排、
埋牌与出牌两条操作条及其中的 CD_SELF 槽位、倒计时三处落点、浮层四类元素的
屏幕位置),另附元素说明、时序说明、资源说明、精灵 ID 分配与 @see 出处。

纯注释改动,数据部分一字未动(精灵总数仍 378、布局节点仍 61)。
如实记下一处缺口:出牌阶段「提示」按钮已有精灵但无布局槽位,待设计稿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 01:48:23 +08:00
joywayerandClaude Opus 5 ccc41ce092 二七王:修正 Sprites_Table/Cards 注释里 ASCII 版式图的对齐
上两个提交的三处版式图在等宽字体下右边框错位(手牌区示意框、结算底牌区
文案行、已出牌区的牌角标小图)。按「CJK 双宽、其余单宽」重排到等宽,
不改任何数据。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 01:48:14 +08:00
joywayerandClaude Opus 5 a4b12a1ebb 二七王:Sprites_Cards.js 补 4 个 View 的布局说明注释
为 HandView / BottomCardsView / BuryCardsView / PlayAreaView 各补一块 JSDoc:
分区标题标出 Layer 与 Group,正文含 ASCII 版式图(手牌单排/双排两种形态、
底牌背面与翻正面、埋牌底牌在结算面板内的位置、三家已出牌区的镜像关系与
牌型标签/牌角标贴附方式)、元素说明、资源说明、精灵 ID 分配与 @see 出处。

纯注释改动,数据部分一字未动(精灵总数仍 378、布局节点仍 61)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 01:39:49 +08:00
joywayerandClaude Opus 5 19f93bb96f 二七王:Sprites_Table.js 补 4 个 View 的布局说明注释
按参考图与 UI 清单,为 TopInfoView / PlayerMarkView / LiangPaiView / FooterView
各补一块 JSDoc:分区标题标出 Layer 与 Group,正文含 ASCII 版式图(标注各区域
对应的精灵常量与实测坐标)、元素说明、资源说明、精灵 ID 分配与 @see 出处。

纯注释改动,数据部分一字未动(精灵总数仍 378、布局节点仍 61)。
顺带如实记下两处文档缺口:群组 206 的命名(亮牌条 vs 主牌统计条)与
底栏「扣底」按钮尚无精灵。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 01:37:44 +08:00
joywayerandClaude Opus 5 0c58ae67db 二七王:倒计时与结算得分改用数字图精灵,收尾几处命名与文档订正
- CD_LEFT/CD_RIGHT/CD_SELF(1150-1152)由文字精灵改为多帧图数字精灵:
  资源 NUM_COUNTDOWN,纯秒数无后缀;对应布局节点 P_COUNTDOWN 的 h 改取
  NUM_STYLE.COUNTDOWN.charHeight,w 留到运行时注入。
- 结算得分 RESULT_SCORE_LEFT/RIGHT/SELF(1805-1807)同理由文字精灵改为
  数字图精灵(NUM_RESULT_WIN/NUM_RESULT_LOSE),与大局结算已有的
  ACC_TPL_SCORE_NUM 保持一致;布局节点随之调整,移除不再使用的
  TEXT_STYLE.SCORE_BIG。
- NUM_CALL_SCORE 第16帧由"待美术与规则确认"改为明确的"无后缀"(叫分档位
  面板只显示纯数字,"N子"角标是独立文字精灵);NUM_MAIN_SUIT_COUNT /
  NUM_RESULT_WIN / NUM_RESULT_LOSE 第16帧仍待确认,不改。
- Layout_Action.js 的 CALL_BTN_SCORE_TEXT、Layout_Result.js 的
  RESULT_SCORE_TEXT 改名为 CALL_BTN_SCORE_NUM / RESULT_SCORE_NUM:
  两者驱动的都是数字图精灵,_TEXT 后缀误导;同步测试引用。
- docs/client/development-guide/02:订正"SpriteManager 第1154行"这一失效
  行号引用(回退后该行号落在 setTextWithWidth 体内),改为不写死行号。
- docs_dev 清单同步:§2.4/§5.4/§5.6/§6.6/§6.9 倒计时与结算得分的精灵类型
  描述、§6.6/§6.9 对应布局参数。
- test_constants.js 的 NUM_STYLE 同源守卫表新增 COUNTDOWN、RESULT_WIN、
  RESULT_LOSE 三对(连同改名后的 CALL_BTN_SCORE_NUM),共 6 条新断言。

精灵总数、布局节点总数均未变(378 / 61),只改类型与命名,不增删。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 00:54:42 +08:00
joywayerandClaude Opus 5 857bc955bb 框架:订正 setNumberImage 帧序注释的自相矛盾(1-based)
方法头「帧序号:0-9对应数字,10='.'...」与方法体内「字母'b'-'g'→帧10-16」
互相矛盾,且均与代码实际不符:'0' 的 charCode 48 减 47 得帧1,'.'→'b' 的
charCode 98 减 87 得帧11,故正确读法是 1-based:帧1-10=数字0-9、
帧11='.'、帧12='+'、帧13='-'、帧14='x'、帧15='/'、帧16=可变后缀。
补充说明编码表故意跳过 'a'(97-87=10 会与 '9'→帧10 撞帧)的原因。
只改注释,不动一行代码。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 00:47:00 +08:00
joywayerandClaude Opus 5 717e405292 二七王:SubGameHooks 注释去掉已作废的 NumberRenderer 提法
事件链路本身不变,只把注释里对已回退方案的引用换成实际存在的例子
(248 号协议勾选的绘制回调)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 00:07:55 +08:00
joywayerandClaude Opus 5 35ee85e1d6 二七王:回退自造的图集叠绘,改用平台既有 SpriteManager.setNumberImage
方案作废并纠正认知:多帧图片精灵【不是】只能显示一位数——SpriteManager 第 1154 行
早有 setNumberImage(spriteId, text, charWidth):把整串文本设给一个多帧图片精灵,
引擎按字符逐帧渲染,精灵宽度按「字符数 × charWidth」自动调整。bc75e40 造的
drawImageRegion + NumberRenderer 是重复轮子,本次整体回退(revert bc75e40)。

- 回退:删除 gameabc-framework/ui/NumberRenderer.js 与其单测,SpriteManager 与
  index.html 恢复到改动前(与 bc75e40^ 逐字节一致)。
- ImageResources.js:数字类资源改为 16 帧、固定帧序 0123456789.+-x/p,逐条写明
  各帧含义、第16帧后缀(选主张数=「张」;结算/叫分的后缀标注待美术与规则确认,未瞎填)、
  单字符宽高与整图尺寸(16 × 单字宽),并说明帧序不可重排、不可省略占位。
- LayoutConstants.js:NUM_STYLE 改为 setNumberImage 所需的 res/charWidth/charHeight/suffix。
- Layout_Action.js:两个数字节点 h 取 charHeight(SSOT),w 留运行时注入
  (宽度由 setNumberImage 按字符数自动改,随位数变)。
- Sprites_Action.js / Sprites_Result.js:注释改为 setNumberImage 用法;
  结算玩家栏 ACC_TPL_SCORE_NUM 是复制精灵也能用(该接口不依赖绘制回调)。
- test_constants.js:NUM_STYLE 守卫改为校验 charWidth/charHeight/suffix 与
  「h 同源、w 运行时注入」;suffix 限定为 setNumberImage 支持的 分/倍/张。
- 文档:清单 §1.3/§1.5/§3.6/§5.4/§6.8 与前端 02 的那一节全部按 setNumberImage 重写,
  三种机制改为「文字精灵 / setFrame 切帧 / setNumberImage」,并记下这次误判作为反例。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 00:07:35 +08:00
joywayerandClaude Opus 5 dbdd6607d7 二七王:文档同步图集叠绘数字(清单 §1.3/§1.5/§3.6/§5.4/§6.8 + 前端 02 新增一节)
- docs_dev 清单:§3.6 数字资源改为「等宽连续排列的一张图」并给出字符顺序/单字宽高/整图尺寸,
  §1.3 说明叫分档位分数为何必须改叠绘(14 档里 13 档两位数、每档一个精灵),
  §1.5/§5.4 选主张数由 8 个精灵改回 4 个(1678–1681 记跳号不复用),
  §6.8 布局节点合并 + 新增 NUM_STYLE 数字样式表;各处均带 2026-08-26 修订说明与缘由。
- 前端 02:新增「三种显示机制:文字精灵 / 多帧图片精灵 / 图集叠绘」一节,
  给出三问选用判据、多位数误用 setFrame 的坑与真实事故、叠绘的三条前提与出图规格;
  §5 补一条「转发必须接上且参数对齐,否则事件静默失效」的警示;DO/DON'T 各补一行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 23:56:32 +08:00
joywayerandClaude Opus 5 2fe0f661b4 二七王:数字改回「一个数字一个精灵」,数字资源改为等宽连续图
有了图集叠绘能力,多位数不再需要每位一个精灵:

- Sprites_Action.js:选主花色张数从每花色两个(_TENS/_ONES)改回每花色一个
  (MAIN_SUIT_COUNT_1..4 = 1674–1677),空出的 1678–1681 按本文件既有写法记跳号、不复用;
  叫分档位 CALL_BTN_NUM_1..14 数量不变,注释改为图集叠绘(现在能正确显示两位数)。
- ImageResources.js:NUM_COUNTDOWN / NUM_RESULT_WIN / NUM_RESULT_LOSE / NUM_CALL_SCORE /
  NUM_MAIN_SUIT_COUNT 不再是编辑器配帧的多帧资源,改为等宽连续排列的一张图,
  逐条写明字符排列顺序、单字符宽高、整图尺寸(给美术的出图规格)。
- Layout_Action.js:张数的十位/个位两节点合并为一个;两个数字节点的 w/h 直接引用
  NUM_STYLE(SSOT),不再由调用方运行时注入。
- Sprites_Result.js:记下 ACC_TPL_SCORE_NUM 是复制精灵、无法用 NumberRenderer 的风险。
- 单测:精灵总数 382→378、布局节点 62→61(按实际更新,未放宽下界);
  新增 NUM_STYLE 资源键/字段合法性与「布局节点 w/h 与样式同源」守卫。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 23:53:16 +08:00
joywayerandClaude Opus 5 3789e0814e 二七王:接通平台精灵事件链路(SubGameHooks → SpriteEventController)
SubGameHooks 原是空骨架,Game_Modify.* → SubGameHooks.* → SpriteEventController
这条链是断的:不仅数字叠绘的绘制回调不会触发,转发壳里已注册的 13 号战绩按钮、
150 号帮助按钮、248 号协议勾选也收不到事件。

实现 utlmousedown / utlmousedown_nomove / mouseup / utlmousemove /
utlgamemydrawbegin / gamemydraw 六个 hook,只做转发、不写业务逻辑,
参数顺序逐个对齐 SpriteEventController.handleXxx 的签名(错位会让事件静默失效),
转发前判断依赖存在,防御风格与转发壳一致。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 23:53:03 +08:00
joywayerandClaude Opus 5 bc75e40acc 二七王:新增图集叠绘数字能力(SpriteManager 源矩形 API + NumberRenderer)
多帧图片精灵一次只显示一帧=只能显示一位数,两位数就得两个精灵,
14 个叫分档位(13 档两位数)每档只配一个精灵时根本渲染不出来。

- SpriteManager.drawImageRegion:把 GameABCUtils.Draw.drawImage 的源矩形
  能力暴露到业务层(drawImage 只透传了整图版),校验/日志/返回值风格照现有
  drawImage 一致;注明必须在精灵绘制回调中调用。
- gameabc-framework/ui/NumberRenderer.js:游戏中立,bind/setValue/clear/unbind,
  绘制回调里按字宽逐位裁源矩形画在同一个精灵上;layout 抽为纯函数可脱离引擎单测;
  字符映射不到显式抛错,不静默跳过。
- EQW_Layout.NUM_STYLE:5 套数字的字宽/字高/字间距/对齐/绘制区宽集中配置(零裸值),
  资源只存键名(与 CARD_SIZE.res 同一约定)。
- client/tests/test_numberrenderer.js:43 checks(多位/单位/正负号/三种对齐/负字间距/
  空值/自定义 charMap/非法字符与非法样式抛错/5 套真实样式画得下)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 23:52:56 +08:00
joywayerandClaude Opus 5 b81e800f22 二七王:选主面板按美术实际出图方式(一张图5帧)重做资源与精灵定义
BTN_SUIT_CHOOSE 改为 5 帧(4 花色按钮各含底+图标+角标底、帧5=投降按钮含底+文字),
删除独立的 SUIT_ICON_L(512)/BTN_SURRENDER(534)/花色图标精灵/角标底精灵/投降文字精灵;
花色张数从单个文字精灵改为图片数字精灵(十位+个位),新增资源 NUM_MAIN_SUIT_COUNT(636)。
同步规格清单文档与两个测试文件里被钉死的精灵总数(387→382)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 23:30:12 +08:00
joywayerandClaude Opus 5 be3e8d0837 二七王:清单 §5.2 手牌标记改为每张牌一个
原表「牌型分组标记 ×8」与 §1.6「三种标记互斥、每张牌至多一个」矛盾。
裁决按每张牌——§1.6 是行为规格更权威,且三种标记本就是逐张判定的属性;
实测一手 28 张常带 13 个以上标记,8 个远远不够。

号段 1236–1243 太窄且其后 1250 起已被底牌区占用,改为 2100–2135。
前端 Sprites_Cards.js 与 core/CardMark.js 已按此落地。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:32:10 +08:00
joywayerandClaude Opus 5 503ad4b374 二七王:SeatMap 座位号显式校验,去掉恒真断言
- toDisplay 此前不校验入参:toDisplay(seat, undefined) 会算出 NaN,NaN 既不等于 0
  也不等于 1,最后落到 return 'LEFT'——一个看起来很合理的错误答案。现与兄弟方法
  toSeat 一样对非 0/1/2 显式抛错;toSeat 的 mySeat 同样校验(否则算出 NaN 座位号)。
  补 8 条反面用例 + 1 条「合法组合不被误伤」的正面用例。
- test_cardcodec.js 删掉 t.eq('deck1/deck2 全部同帧', true, true) 这条恒真断言,
  改为收集不匹配项后断言为空——恒真断言在上面的循环被删掉后仍会 PASS。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:27:05 +08:00
joywayerandClaude Opus 5 d1b60275a4 二七王:建房选项配置补精灵键、bit 统一挂到 item、补 || {} 保护声明
Sprites_CreateRoom.js 的注释说「配置里每个选项声明自己用哪个精灵键」,但 EQW_RoomOptions
的 items 只有 label/value,渲染器只剩「按下标拼字符串」一条路——而那正是同文件明文禁止的;
何况也拼不对(组的 key 是 'rules',精灵却叫 ROOM_BOX_RULE_1)。

- 每个类别加 titleSprite,每个选项加 box/text 精灵键,房卡附注加 noteSprite。
- bit 统一挂到每个 item 上(单选组各项写同一个 bit),拼 roomtype 的循环对
  radio/checkbox 是同一个形状(item → 写 value 到 bit),不再按组/项两种层级分支;
  checkbox 项补上 value:'1'。
- 补上其他常量文件都有的 `|| {}` 保护性声明。
- 守卫新增:精灵键必须存在且互不重复、必须恰好覆盖建房 View 预置的全部选项精灵、
  bit 不得再挂在组上、单选组内 bit 一致、多选组 bit 互异、位号刚好铺满 0–4
  (对齐服务端 class.config.js 的 IDX_ASET..IDX_NOCHECK)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:26:21 +08:00
joywayerandClaude Opus 5 ee65871694 二七王:手牌标记精灵扩到与手牌一一对应(36 个),并钉死精灵总数
清单 §5.2 表格写的是「牌型分组标记 ×8」,但 §1.6 明确规定三种标记互斥、【每张牌】
至多一个,core/CardMark.js 也是按每张牌返回标记的。一手 28 张牌通常带 13 个以上标记、
36 张埋牌手更多,8 个远远不够。§1.6 是行为规格、比表格措辞更权威,故按 §1.6 裁决:
HAND_MARK_1..36 与 HAND_CARD_1..36 一一对应。

- ID 另起 2100–2135 整段空号:1236 往后接不下 36 个连号(1250 起已是底牌/埋牌区),
  换空号段比给别的 View 重新编号更安全;仍落在子游戏段 1001–2999,无重复。
- 守卫补「手牌与标记 1..36 一一对应且同群组」检查。
- 精灵总数由「> 200」改为钉死 387(test_constants / test_spriteindex 两处同一个数),
  布局节点数同理已钉死——松下界删掉整个 View 都还能通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:25:19 +08:00
joywayerandClaude Opus 5 92edb6a3f5 二七王:布局求解器补齐显式失败与运行时注入契约,配置补 runtime/targetKind 声明
求解器(ui/LayoutSolver.js):
- 按方向校验 anchor:竖排只收 top/bottom/center、横排只收 left/right/center,
  未知 direction 一并拒绝。框架 AlignmentUtils 的 switch 对写错方向的 anchor 落到
  default(居中),笔误会被静默吞掉——ROOM_CATEGORY_COLUMN 就是这么把前两行摆到画布外的。
- grid 校验合并 ctx 后的 cols/rows 必须是数字:缺 rows 时 capacity=NaN 让超载守卫失效、
  循环一次不跑,静默返回空数组(一个矩形都不产出)。
- line/fan/grid 要求 anchorX/anchorY 必须是数字(框架的 anchorY||0 会把缺失变成 0);
  point 要求 x/y 必须是数字。
- apply() 精灵数与矩形数不等时抛错,不再静默截断:截断会让多余精灵停在上一手牌的旧坐标上。
- 统一运行时注入:ctx 里的 INJECT_KEYS(target/x/y/w/h/anchorX/anchorY/rows/
  itemWidth/itemHeight)覆盖配置同名字段,优先级 ctx > bySeat > base。
- line + items 竖排显式拒绝(只实现了水平)。

配置:
- ROOM_CATEGORY_COLUMN anchor 由 'left' 改为 'top'(竖排语义)。
- ROOM_OPTION_OVERFLOW_GRID 补 rows(runtime)、槽尺寸与 anchor 沿用 ROOM_OPTION_ROW,
  cols 直接引用 ROOM_OPTION_MAX_PER_ROW;ROOM_OPTION_ROW 补上注释里已声明的 itemHeight。
- 所有依赖运行时注入的节点加 runtime 声明,所有 attach 节点加 targetKind
  (sprite / layout / platform),显式区分「忘了写」与「故意延后到运行时」。

守卫(tests/test_constants.js):
- 求解冒烟改为只对 runtime 声明过的键注入假值,没声明却缺字段的照常抛错变红。
- 补矩形数量断言(line/fan/grid 按项数、point/attach 恒 1)——此前只查数组与坐标类型,
  空数组照样通过,「少画了几张牌」对守卫完全不可见。
- attach.target 改为只在 targetKind 指定的那一个命名空间里校验存在。
- 补 runtime 声明自身的合法性检查与布局节点总数(62)钉死。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:24:16 +08:00
joywayerandClaude Opus 5 b81be36f6e 二七王:拖拉机极大连续段扫描与牌编码区间常量收敛进 shared
前后端此前各写了一遍「分对子 → is_continuous 扫极大连续段 → ≥2 对算拖拉机」:
服务端 class.arith.js decompose_trump 与前端 core/CardMark.js。规则内容(什么算连续、
几对起算拖拉机)散在两处,正是 shared/ 要消除的重复。

- shared/cards.js 新增 group_tractor_runs(mainflower, pairlist):返回极大连续段数组,
  孤立对子作为长度 1 的段返回,拼接后恰好还原入参;阈值导出为 TRACTOR_MIN_PAIRS。
- shared/cards.js 导出牌编码区间边界具名常量(CODE_ZHU_MIN / CODE_ZHENG2_* /
  CODE_FU2_* / CODE_ZHENG7_* / CODE_FU7_* / CODE_XIAOWANG / CODE_DAWANG /
  CODE_A_IN_ZHU),并在 id_to_code / is_continuous / trump_rank 内部改用它们。
- decompose_trump 改调共享扫描,对外行为完全不变(分量类型、顺序、cards 归属不变)。
- CardMark 删掉本地扫描改调共享版本,区间判定与标记值改引用常量,去掉 1000/3000/
  4000/8000/9000 裸字面量。
- 前端副本经 sync_shared.cmd 同步;服务端单测 624→645 checks 全绿,前端 8 个测试全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:17:09 +08:00
joywayerandClaude Opus 5 b22558d0b3 二七王:前端接入骨架与 index.html 加载段
SubGameHooks 从模板复制、本阶段保持空骨架(无 hook 时转发壳走平台默认)。
index.html 按 client 06 §3 的顺序加入 codes 段:config → shared → core → ui
→ SubGameHooks → 三文件转发壳,三个转发壳一字未动。
Node vm 依序加载验证无异常,全部 EQW_* 与 SubGameHooks 就绪;两端测试全绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:51:53 +08:00
joywayerandClaude Opus 5 a05cc7b7fa 二七王:常量层机械守卫测试补 ctx.w/ctx.h 求解冒烟兜底
求解冒烟第一次跑真实跑出 9 个节点(叫分档位分数文字、花色数量文字、牌角标、
结算文案等)因配置有意不写 w/h(真实宽高等文案渲染出来才知道)而求解失败;
裁决方向是给 LayoutSolver 补 ctx.w/ctx.h 运行时注入通道(已由 6390ce7 落地,
与既有 ctx.target 对称)。本次让冒烟测试的合成 ctx 对称跟进:仅在配置(含
bySeat 覆盖后)确实缺失 w/h 时才注入假尺寸,不覆盖配置自带的值,保证两条
路径都被真实覆盖到。60 个布局节点、84 次求解全部通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:46:41 +08:00
joywayerandClaude Opus 5 6390ce74c2 二七王:LayoutSolver attach 加 ctx.w/ctx.h 运行时尺寸注入通道
求解冒烟守卫拿真实布局配置跑出 9 个失败节点:叫分档位分数文字、花色数量
文字、牌角标、结算文案等动态文字标签配置里有意不写 w/h(真实宽高要等
文案渲染出来才知道),此前的按对齐模式校验因此在 hAlign=center/right、
corner=topRight/bottomRight 时正确地抛错,但没给调用方注入真实尺寸的通道。

补 ctx.w/ctx.h,语义与已有的 ctx.target 对称:优先于配置里的 n.w/n.h,
校验与求解都用合并后的值,返回的 width/height 也用合并后的值(不再是
可能为 undefined 的配置原值);两处都缺且用得到该维度时仍抛错,错误信息
点明"配置未写 X,且 ctx.X 未给出"两条路径。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:43:33 +08:00
joywayerandClaude Opus 5 ec954ea4fe 二七王:补建房折行 grid 的 cols 字段,补庄标 kind 覆盖注释
按复审意见修 2 处 Minor:ROOM_OPTION_OVERFLOW_GRID 补上清单原文给出的
cols:maxPerRow;P_BANKER_MARK.bySeat.SELF 补注释说明有意将 kind 由
attach 覆盖为 point(底栏庄标是定点,不贴附头像框,与左右上家不同)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:35:44 +08:00
joywayerandClaude Opus 5 949702dc90 二七王:Task 13 守卫加入「求解冒烟」
拿真实布局配置逐节点实际调用求解器,断言不抛错且坐标非 NaN。
字段名写错、缺 w/h、kind 不认识、bySeat 缺座位都会当场炸出来,
而不是等到界面上静默摆歪——这是配置与求解器之间契约的最强守卫。

attach.target 的解析范围同时改为三者并集(子游戏精灵 / 布局节点名 / 平台精灵)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:32:48 +08:00
joywayerandClaude Opus 5 91f0eb116c 二七王:LayoutSolver attach 支持运行时 target 注入,按对齐模式校验 w/h
配置落地后暴露两个缺口:叫分档位/花色图标/牌角标等 attach 目标要运行时
才确定,配置写不出固定 target,需要 ctx.target 覆盖/补全(优先于 n.target,
两者皆无则抛错);纯文字标签没有固定 w/h,此前缺失时会静默算出 NaN 坐标。
改为按本次实际走的对齐分支(hAlign/vAlign/corner)逐一核对 AlignmentUtils
源码确认是否用到 sprite.width/height,只在用得到时才要求对应字段是数字,
不用得到时(如 left/top/bottom、topLeft)不强制,避免逼纯文字配置编造假值。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:29:31 +08:00
joywayerandClaude Opus 5 6c92920df8 二七王:前端布局与动画常量
照清单 §6 各表逐条录入,字段名与 LayoutSolver 的 node 一一对应、可原样喂入。
布局文件保持纯数据:无函数、无副作用;分行策略等写成数据标记而非函数。
建房选项配置自带 bit 与 value,roomtype 拼串遍历配置即可,不按下标硬拼。
动画参数含 D-8 判定动画的待补说明。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:26:16 +08:00
joywayerandClaude Opus 5 1976edf9bd 二七王:前端 SpriteIndex,精灵键名扁平索引
布局配置按键名引用精灵,本模块把嵌套的精灵常量展平供查。
重复键与未知键一律抛错——静默覆盖会让界面指向别处的精灵且极难排查。
用例含真实常量的全量建索引校验。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:06:15 +08:00
joywayerandClaude Opus 5 4d05457590 二七王:拆分 HistoryMingPaiView,补充注释细节
拆分理由:Layer1/Layer2 的写法让 History/MingPai 两个 group 容器各自
属于哪个图层只靠书写顺序隐含,无结构绑定。拆成 HistoryView(Layer 304)
与 MingPaiView(Layer 305) 两个独立 View 后,「一个 View 恰好一个 Layer」
成为不变式,精灵键名/ID/内容不变。

顺带补两处注释 Minor:PlayAreaView 的 PlayLeft/PlayRight 块首补资源键
说明;AccountView 的 4 个功能按钮改为逐条写资源键,不再靠顺序隐式对应。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:02:20 +08:00
joywayerandClaude Opus 5 69cb04ecfa 二七王:确立「一个 View 恰好一个图层」不变式
原本允许 Layer1/Layer2 声明多图层,但「哪个 group 属于哪个图层」只能靠
注释隐含、无法机械判定。改为跨图层的界面拆成两个 View,各带自己的 Layer——
一个 View 将来对应一个 BaseComponent 组件,本就该分开。

SpriteIndex 加校验守住该不变式:View 缺 Layer、或写成 Layer1/Layer2 均报错。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 21:01:04 +08:00
joywayerandClaude Opus 5 488f36b545 二七王:前端精灵结构常量
照清单 §5.1–§5.8 逐条录入,键名与 ID 原样照抄;省略写法(如「结构同 202」)
全部展开写全。每个精灵注明类型/用途/资源键/帧说明,后期照此在编辑器里创建。
按协调方中途下达的结构变更,Group 与其名下精灵合并为 group 容器
(groupName: { id: 群组常量, 精灵键: 精灵ID, ... })。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:53:35 +08:00
joywayerandClaude Opus 5 8b579896b2 二七王:精灵常量再优化为 View → group 容器 → 精灵
group 与其名下精灵合并为 { id: 群组常量, 精灵键: id, ... },
所属关系由结构表达而非注释;拿到 group 对象即可 showGroup 并遍历其精灵。

SpriteIndex 随之支持 groupIdOf:精灵与群组 ID 被结构绑定,
显隐走群组时可机械查出。校验增至 5 处显式失败(重复键、精灵值非数字、
View 下混入标量键、group 缺 id、查不到的键)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:51:18 +08:00
joywayerandClaude Opus 5 5ac445d08d 二七王:精灵常量改为以 UI View 为组织单位
Layer/Group 扁平声明在 View 定义下,精灵只写 id、所属关系写在注释里;
一个 View 对应一个 UI 组件,拿到 View 即拿到它的全部精灵与图层群组。

SpriteIndex 的展平逻辑随之调整:保留键 Layer*/Group* 跳过,其余键即精灵;
新增「值非数字即报错」——两者结构上同级、只靠键名区分,写错前缀会静默变成假精灵。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:44:17 +08:00
joywayerandClaude Opus 5 6546f75965 二七王:前端图层/群组/图片/声音常量
照清单 §0.3 与 §3 逐条录入,图片资源每条注明用途/帧数/帧义/尺寸——
后期即照这些注释在编辑器里建资源。声音因 T-20 未决只建骨架、不臆造条目。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:22:24 +08:00
joywayerandClaude Opus 5 8d6d272f76 二七王:LayoutSolver grid 补显式失败校验,attach 四角补精确坐标测试
审查发现 _solveGrid 在 count > cols*rows 时静默截断多出的项,违反显式失败
红线;补校验直接抛错并报出 count 与容量。fillOrder 字段之前从未被读取、
形同虚设,改为显式拒绝除 'row' 外的值(不实现列优先,YAGNI)。
测试补 grid 超载/恰好满格/fillOrder 三态,以及 attach 四角(topLeft/
bottomLeft/bottomRight)的精确坐标核对(此前只有 topRight 有精确值)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:15:57 +08:00
joywayerandClaude Opus 5 2d0469e7f3 二七王:前端 LayoutSolver,五型布局求解器
solve 是纯函数(attach 靠 ctx.rects 查目标矩形而非反查引擎),
fan 的 clamp 间距算法只此一处实现;line 带 items 时自行按累计宽度推进,
不走假设等宽的 distribute。未知 kind/缺参/target 缺失一律抛错,不兜底。
apply 是唯一触碰 SpriteManager 的三行封装。

测试里 attach 外侧右上角一条按 AlignmentUtils.alignCornerTopRight 的真实实现
(角对角重合,非贴外侧)修正了期望值,未在求解器里做偏移补偿迁就猜测值。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:09:26 +08:00
joywayerandClaude Opus 5 3a5da5b975 二七王:前端 CardMark,手牌标记推导与花色统计
三种标记互斥、优先级 tractor > zheng > zhu,据主花色本地推导。
拖拉机识别与服务端 decompose_trump 同思路:分对子后扫极大连续段,
主牌一组、副牌按花色分组。反面用例覆盖不连续、单对、跨花色三种不成拖的情形。
countByFlower 供选主面板的张数与对数,协议明确服务端不下发。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:02:44 +08:00
joywayerandClaude Opus 5 39a920bed5 二七王:前端 CardOrder,手牌主牌序排序
直接委托 shared/cards.js 的 order_cards(与服务端同一份文件),
前端只负责 concat 防改入参、把未选主表达为 mainflower=0。
单测拿服务端 class.arith 的输出做逐一对照,五种主花色全覆盖。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 20:01:51 +08:00
joywayerandClaude Opus 5 e9333303d5 二七王:前端 SeatMap,服务端座位与显示位互换
清单 §0.1:下家 (seat+1)%3 显示在右上,与服务端 get_nextseat 同源。
3×3 全组合 + 互逆 + 非法键抛错都已覆盖。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:57:02 +08:00
joywayerandClaude Opus 5 65a73c5c2a 二七王:前端 CardCodec,cardId 转牌面帧号
清单 §0.4 的唯一权威转换:服务端花色段序与美术帧顺序相反,
转换把花色段反过来数。用清单给的 10 条样例逐条锁死,
另加两副牌同帧、值域、单射三组断言。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:56:32 +08:00
joywayerandClaude Opus 5 3b9828c8ca 二七王:修正文档里服务端测试脚本数量
实际是 17 个 test_*.js(624 checks),原文写的 20 把 _assert/_rpc/_shim 也数进去了。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:52:31 +08:00
joywayerandClaude Opus 5 656066b1a5 二七王:前端单测框架与 shared 同步守卫
测试侧用 vm.runInThisContext 加载浏览器全局脚本,正式代码不为测试加任何导出。
test_shared_sync 断言前后端 shared 逐字节一致,已做反向验证(改一个空格即红)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:51:31 +08:00
joywayerandClaude Opus 5 147a28805e 二七王:sync_shared.cmd 补上 chcp 65001
对齐 build_spine_data.cmd 既有范式,修复交互式控制台下中文提示的乱码问题;
计划原文漏抄了这一行。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:47:16 +08:00
joywayerandClaude Opus 5 d287ce5c8c 二七王:新增根目录 shared 同步脚本与前端只读副本
同步是跨端动作(源在 server、目标在 client),脚本放仓库根目录。
只提供服务端→前端一个方向;前端副本只读,手改会被覆盖。
CLAUDE.md 常用命令一节同步收录。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:44:00 +08:00
joywayerandClaude Opus 5 9f32af7d72 二七王:抽出前后端共享算法 shared/cards.js
把牌值编码与牌型分组的 8 个纯函数(id_to_flower/id_to_number/id_to_code/
order_cards/is_continuous/get_pairlist/get_tuolaji_list/trump_rank)移入
shared/,arith 改为挂引用——对外方法名与签名不变,既有调用点零改动。

前端排序与手牌标记将同步这一份,避免两处各写一份导致主牌序漂移。
现有 20 个测试脚本原样全绿,即本次纯搬运的验收依据。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 19:37:46 +08:00
joywayerandClaude Opus 5 e326481f2f 二七王:前端子项目 A 实施计划
14 个任务,每个自带 TDD 循环与提交:shared 抽取与同步(1-2)、
单测框架与漂移守卫(3)、core 四个纯逻辑模块(4-7)、布局求解器(8)、
常量层三批(9/10/12)、精灵索引(11)、常量机械守卫(13)、接入骨架(14)。

顺带修订设计文档两处:shared 抽取函数数量 7→8(原文列了 8 个名字),
CardMark.markOf 签名与计划对齐(拖拉机判定需整手牌作上下文)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 18:55:26 +08:00
joywayerandClaude Opus 5 0f29c2e08c 二七王:shared 同步脚本改放仓库根目录
同步是跨端动作(源在 server、目标在 client),不属于任何一端,
故 sync_shared.cmd 放根目录而非 client/scripts/。

同时写明单向流水线纪律:只改服务端 shared,改完跑脚本同步;
前端副本只读,本地改动会被覆盖,由字节一致守卫测试兜底。
验收补一条:CLAUDE.md「常用命令」需同步收录该脚本。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 18:42:52 +08:00
joywayerandClaude Opus 5 849de06194 二七王:前端子项目 A(地基与逻辑层)设计定案
前端拆为 A→F 六个子项目,本文定案 A:目录结构、常量层全量、
core 纯逻辑、codes/ui 布局求解器、shared 抽取与同步、单测框架、接入骨架。

两处取舍已定:求解器归子游戏 codes/ui;排序算法走服务端 shared/ 同源
(抽 7 个牌值编码与牌型分组函数,arith 改挂引用,现有 20 个测试为安全网)。

资源与精灵不阻塞编码——常量文件先行、即建资源的规格书,编辑器后期照建。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 18:36:55 +08:00
joywayerandClaude Opus 5 f04bff4e9f 二七王:S-8 curmultiple 改为带符号
问题:S-1 实现时取了绝对值,而判定的正负号恰恰是「谁赢」的区分——
3 大光 vs -3 升3级、2 小光 vs -2 升2级、1 过庄 vs -1 升1级,取绝对值后
三对完全撞在一起,D-8 的过程中判定动画无从分辨该播哪个。

改法:get_curmultiple 直接返回 get_upgrade 原值,与结算包 aset.upgrade
完全同口径(3/2/1 庄家大光/小光/过庄,-N 闲家升N级,0 叫分未定)。
顶部「抓分」角标只关心大小,取 Math.abs 即可;判定动画靠符号分辨,
前端一套映射通吃「过程中」与「结算前」两条路径。

协议 4 处 curmultiple 说明同步(shangzhuang / chupai1 / chupai2·3 /
PushCards)。

测试(hint 33 → 46 checks):
- 钉住三对「绝对值相同、判定相反」的组合可区分,以及符号语义
  (>0 庄赢 / <0 闲赢 / ==0 叫分未定)。
- 新增一组【直接调 get_curmultiple 本身】覆盖负数分支,含爬坡 Q 分段。

补上一个测试盲点:第一次反向验证(把实现退回取绝对值)竟然全绿——因为
原有断言里算法层的 cm() 走的是 A.get_upgrade、绕过了 get_curmultiple,
而端到端那局恰好是大光(正数),取不取绝对值都一样。补齐直接调用的用例后
再验,4 条断言立刻失败。

至此 §7.5 服务端待补 8 条全部完成,服务端侧无遗留;D-8 只差动画设计稿
与触发细节。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 12:42:29 +08:00
joywayerandClaude Opus 5 fd31272bd1 二七王:D-8 判定结果改为动画呈现,并记录 S-8
小局结算面板【不做】大光/小光/过庄/升N级 的静态文字,改由动画在两个时机
呈现:对局过程中(判定档位跳变时)与结算前(最终判定)。精灵 1814
RESULT_JUDGE_TEXT 与资源 634 的静态文字方案作废,634 改为帧动画资源
ANI_JUDGE,播放参数进 AnimationConfigs。

动画遵守「数据优先、表现延后」:先写数据再播动画,动画回调只刷界面不写
核心数据;动画缺失或卡住时结算面板与数据仍正确。

新增 S-8(阻塞 D-8 的过程中动画):S-1 实现 curmultiple 时取了绝对值,
而判定的正负号恰恰是「谁赢」的区分——取绝对值后
  3 大光 vs -3 升3级、2 小光 vs -2 升2级、1 过庄 vs -1 升1级
三对完全撞在一起,过程中动画无从判断该播哪个。建议改为带符号、与结算包
aset.upgrade 同口径,前端一套映射通吃两条路径;顶部抓分角标取 Math.abs
即可,不受影响。

当初取绝对值是因为 S-1 只服务顶部角标一个用途、只关心大小;现在多了动画
这个消费方,符号成了必需信息。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 12:12:58 +08:00
joywayerandClaude Opus 5 e51cde02b8 二七王:去掉开底提示文案,状态提示条收敛为 3 条
闲家看到底牌由背面翻成正面本身就是开底最直白的表达,再加一行「底牌公示中」
是多余的。移除后提示条只剩 3 条:等待庄家选主 / 等待庄家埋牌 / 强甩失败。

「不做」清单相应增至五处:叫分等待、出牌等待、准备等待、掉线重连、开底提示,
各自记明理由(倒计时已指明、平台标识已有、画面本身已表达)。

同时移除随该文案而设的「底牌公示是文案不是术语」那段注记——文案没了,
注记也不再需要;术语侧「开底」的定义在 §0.0 未变。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 08:20:48 +08:00
joywayerandClaude Opus 5 32cbe2fe91 二七王:T-33 提示条文案定案,亮牌改为纯自动关闭
T-33 定案,状态提示条全部 4 条:
  底牌公示中   全场   70 分开底那 3 秒
  等待庄家选主 闲家   shangzhuang → xuanzhu
  等待庄家埋牌 闲家   xuanzhu → maipai
  强甩失败     全场   收到 shuaicuo==1,自动淡出

明确不做四处并记下理由:叫分等待与出牌等待(倒计时就显示在当前操作者
位置旁,已指明在等谁;出牌一局约 84 次切换,弹条只会吵)、准备等待
(平台自带已准备/未准备标识)、掉线重连提示(平台 Layer 615 已覆盖)。

特别标注一处易绕回去的地方:「底牌公示中」是【界面文案】可以用,但
「底牌公示」作为【术语】已弃用(会与「亮牌=公示牌力」混淆),规则术语
一律说「开底」。文案配合 8 张牌摊开的画面不会误读,故沿用。

T-34 补充:亮牌面板改为【仅自动时长关闭】,遮罩不注册点击。并列出三个
遮罩面板关闭方式的对照表——亮牌仅自动、冲关牌型点遮罩、明牌/出牌历史
点遮罩+按钮切换,三者不同,实现时最容易写混。

至此待确认 34 项只剩 T-20 音效(已明确后补)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 08:14:51 +08:00
joywayerandClaude Opus 5 6c78b37e89 二七王:T-34 亮牌展示时机定案,整理 T-33 等待提示条文案候选
T-34 已定:亮牌在【埋牌后、出牌前】一次性弹出——收 maipai 包(其中带
liangpai)时展示,出牌开始前收起,与 design §8.2 的规则时点一致。
点遮罩关闭 + 自动关闭时长兜底(liangpaiAutoClose 进配置);只有闲家会
收到并弹出;面板开着不阻塞出牌推送,收到出牌包直接收起即可。

T-33:按流程梳理出等待类文案候选 W1–W6 写进 §2.8,连同即时反馈类
(目前只有「强甩失败」)。同时标出三处需要拍板的:
- W4 出牌等待要不要做——一局约 84 次切换,每次弹条会很吵,且已有倒计时
  与已出牌区两重指示,倾向不做。
- W6 开底 3 秒显示什么——参考图上写的是「等待庄家选主」,但那张图叠画了
  开底与选主两个状态(§0.5),不能直接采信。
- 文案是否带昵称——带则提示条需随文案变宽。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 08:07:35 +08:00
joywayerandClaude Opus 5 796503153d 二七王:S-7 明牌条件按手册定案,服务端无需改动
结论:按 design §9.3——任一玩家报无主后,三家都显示「明牌」按钮,
且整个出牌阶段随时可查、不限次数。判定依据是「场上有人报无主」而非
「自己报无主」,报无主那位和另外两位一样能看。

此前我把前端条件收紧成「自己是无主玩家」,比手册严格,反而与服务端
have_baofu() 的受理条件对不上。现改回:

- 前端清单 §1.7 D-6、§2.6 底栏按钮、§0.0 对照表、baozhu 字段说明统一为
  「可查牌 + 出牌阶段 + baozhu==1 → 三家都显示」。
- design §9.3 第 3 条补一句消歧:明确「为这三个人都出现」、判定依据是
  场上有人报无主、且不限次数随时可查。原文靠「同时」承接上一条的
  「为全体三人」,容易读漏。
- protocol 的术语表与 mingpai 受理条件说明同步加注。

服务端与协议本来就是手册口径,无代码改动。此前记的「有主牌玩家伪造请求
也能拿到明牌数据」这一泄露担忧不成立——按手册他本就有权查看,服务端受理
是正确行为。

验收清单增至 18 条,新增一条专门钉住「三家都显示,不是只给自己无主那家」。
至此 §7.5 服务端待补项 7 条全部处理完毕,服务端侧无遗留。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 08:03:08 +08:00
joywayerandClaude Opus 5 9ea6bfa14d 二七王:立「怎么读参考图」原则,清掉由 mock 数值引出的伪待确认项
参考图是界面设计示意,只看版式/位置/样式,不看数值与状态组合。§0.5 新增
一张对照表写清哪些可信、哪些不可信:

  可信:位置、尺寸、层级、配色、字体、元素形态、牌面排布方式
  不可信:具体数值(一律填充假数据);同时出现的互斥元素(如多个玩家位
          同时挂庄标——那是在标「庄标在每个位置分别摆哪儿」);跨阶段并置
          的状态;底栏功能按钮的显隐

据此关闭两个本来就不成立的待确认项:
- T-31 大局结算「基础分」与「总得分」:图上两者数值相同且与右侧大字对不上,
  是填充数据所致。取 grade_jf_total 与 score,S-6 已实现。
- T-32 等待庄家选主.png 里底牌仍摊着:属叠画示意。实际先后仍按 design §4:
  开底 3 秒 → 摸底入手 → 选主。

其余三处已按同一原则处理过的表述(叫分角标 1子/2子/4子、埋牌双排 17/19)
统一改为指向 §0.5,口径一致。

顺带整理 §7.2:T-9 已定却仍留在「仍未决」表里,T-30~T-34 又不在表内。
现重整为「仍未决 3 项(T-20 音效 / T-33 等待文案 / T-34 亮牌展示时机)」+
「已定论 31 项」,并核对 T-1~T-34 连续无缺号。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 07:46:13 +08:00
joywayerandClaude Opus 5 76e82405e1 二七王:S-6 大局结算按 基础分/冲关分/傍王分 三项分解
问题:大局结算面板要分项显示,而 account 只有「累积得分 + 每局得分数组」。
傍王分尤其拆不出来——N = 冲关奖数 + 傍王王数,两个来源【求和之后】才代入
grade_aw = X×(2Ni−Nj−Nk),事后无法反推各自占比。

解法:在算钱时就拆。把 N 分成 N_冲关(只计庄家)与 N_傍王(勾选后庄闲都算),
分别代入同一公式各算一次。公式对 N 线性,故恒有
  grade_cg + grade_bw == grade_aw
这条恒等式正好当回归断言用。

服务端:
- class.paiju.js 抽出 award_pay(n) 复用同一公式,新增 grade_cg / grade_bw;
  desk.seatlist 追加三个累计位(基础/冲关/傍王)。
- class.desk.js seatlist 初始化为 5 元素;account 下发改为【对象数组】
  { score, grades, grade_jf_total, grade_cg_total, grade_bw_total }。
  原二元数组下标语义不清,且前端未开工,此时改代价最小(同 S-4 的判断)。

协议:aset.seatlist 增补 grade_cg / grade_bw;account 结构与恒等式
  grade_jf_total + grade_cg_total + grade_bw_total == score 写明;
  并记录这次结构变更的时间与理由。

测试(endgame 26 → 39 checks):
- 核心不变量:两分量之和 == grade_aw;两分量各自零和。
- 未勾傍王局:grade_bw 恒 0、grade_cg == grade_aw。
- 【新增一整局开傍王的端到端】:不开傍王时 grade_bw 恒 0,只验到平凡情形,
  必须有傍王局才算真验证了拆分。该局逐项复核两个分量的公式、naward 口径,
  并钉住「王数不全相等时 grade_bw 必有非零」防止又退化成平凡。
- 大局累计恒等式:基础+冲关+傍王 == 累积总分。

反向验证:把 _awbw 置零 → 4 条断言失败;把冲关分量误算到闲家头上 →
2 条断言失败。均已还原、全量 17 文件全绿。

另修四处测试桩:_rpc.js / test_paiju / test_rpc / test_success 里手工构造的
o_desk.seatlist 仍是 2 元素,未跟上 class.desk.js 的结构变更(脚本缺陷,
非业务缺陷)。

顺带解决小局结算「冲关分」标签的不严谨:改取 grade_cg 而非 grade_aw,
开傍王时不再把傍王贡献算进来。

遗留 T-31:参考图「基础分」与「总得分」两行数值相同且与右侧大字对不上
(30/30 vs +135),mock 不自洽。当前按 基础分=grade_jf_total、总分=score
提供。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 00:34:46 +08:00
joywayerandClaude Opus 5 50e6fd3ca4 二七王:庄家算奖统一口径为「冲关」
design §8.1 的「常规算奖」全面改称「冲关」,与服务端既有字段 chongguan、
界面「冲关分」「冲关牌型」对齐。理清三层关系并写进三份文档的术语表:

  冲关 = §8.1,只计庄家(旧称常规算奖)
  傍王 = §8.3,可选,庄闲都算
  算奖 = 上位概念 = 冲关 + 傍王 → naward(总奖数)、grade_aw(算奖得分)

「算奖」作为上位概念保留——naward / grade_aw / §8.4 的结算模型都建立在
合并概念之上,去掉它这些字段无从命名。故未做无差别替换,而是逐处甄别:
指 §8.1 的改冲关,指合计的留算奖。

design:17 处「常规算奖」→「冲关」,另修正 §7.0 第 4 层一处笔误
(「8.2 傍王」应为「8.3 傍王」,§8.2 是亮牌);§8.1 开头加改名说明。
protocol:新增 §0.0c 算奖层级表;chongguan / naward / grade_aw 注释同步。
前端清单:界面文案与精灵键名统一(算奖牌型→冲关牌型、BTN_AWARD_CARDS→
BTN_CG_CARDS、AWARD_*→CG_* 等),§0.0 补层级表。

据更新后的 小局结算.png:「算奖分」→「冲关分」、「算奖牌型」→「冲关牌型」。
参考图 算奖牌型显示.png 已随之更名为 冲关牌型显示.png,文档 6 处引用同步;
已逐一核对 16 张图的文件名在文档中都能对上。

顺带记录一处标签不严谨:小局结算的「冲关分」取的是 grade_aw,而后者是按
合计后的 N 算出来的。没开傍王时两者相等;开了傍王时该数含傍王贡献,叫
「冲关分」偏窄。根因与 S-6 同源(两分量算钱前已合并、事后拆不开),待
S-6 落地后此标签才名副其实。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 00:25:11 +08:00
joywayerandClaude Opus 5 b98f58effc 二七王:亮牌改为下发庄家全部固定主牌的具体牌面
规则变更(design §8.2):原先「只亮数量/结构,不亮具体是哪几张牌」,现改为
向两个闲家亮出【庄家手中全部固定主牌的具体牌面】。

- 固定主牌 = 双王 + 全部花色的 2 + 全部花色的 7,【不含】主花色普通牌
  A/K/Q/J/10/9/8/6/5。注意这与旧实现收集的「所有主牌」范围不同。
- 门槛不变(固定主牌总数 ≥10 / 王 ≥3 / 7 ≥6 / 2 ≥6,任一满足)。
- 不限叫分;仅可查牌模式下发;亮出的是固定的一份,不随被哪条门槛触发而增减。
- 协议 liangpai 由 {zhu,zhupair,zhutuo,wang,qi,er} 数量结构改为 {cards:[牌id...]},
  数量交由前端从 cards 自行计算。

服务端 get_liangpai() 重写(class.paiju.js);design §8.2 整节重写,术语表、
易混对照表、§9 相应更新;协议 §0.0b 与 maipai / PushCards 两处字段说明同步。

测试:
- test_paiju 亮牌用例按新规则重写(54 checks)。新增两条针对性用例:
  「只含固定主牌·主花色普通牌未混入」与「同时满足多档仍只亮同一份」——
  前者正是这次范围变更最容易写错的地方。
- test_leak 增加亮牌可见性规则。关键点:期望集合【独立于 get_liangpai 重算】,
  而不是复用被测实现——否则实现若误塞非固定主牌,审计会跟着放行、抓不住。
  反向验证证实了这一点:故意让 get_liangpai 混入主花色普通牌后,审计确实报警。

发现并修复一处假绿:原 6 局随机牌序下庄家一次都没达到亮牌门槛,即
「下发面无越权泄露」对亮牌这条路径根本没走到。现追加换牌序的 4 局(保留
原 6 局覆盖不动),并加两条覆盖断言钉住「可查牌达标 ≥1 局」「不查牌达标
≥1 局」,防止以后再退化成碰巧没触发。反向验证:去掉不查牌门控后审计立即
报出 5 张越权牌。

待定:T-34 亮牌的展示时机(出牌前一次性弹出 / 随时可查)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 23:46:06 +08:00
joywayerandClaude Opus 5 dc9123da92 二七王:确立「摸底 / 开底 / 查底牌」术语,弃用有歧义的「底牌公示」
弃用原因:中文「底牌」既指发牌留桌那 8 张牌,也指"底细/实力"。我上一轮
造的「底牌公示」会被读成"公示庄家牌力",而那其实是 design §8.2 的「亮牌」
——两边撞车。改用「底」字族,与已有的「扣底」成套:

| 术语 | 含义 | 谁看得到 |
| 摸底 | 庄家坐定后把 8 张底牌摸起查看并入手牌 | 仅庄家 |
| 开底 | 70 分坐庄,摸底【之前】向全场翻开 3 秒 | 全场 |
| 查底牌 | 对局中随时回看这 8 张(前端底栏按钮) | 庄家全程;闲家仅开底过的局 |
| 扣底 | 末轮闲家用主牌赢下,翻开埋牌底牌计分(原有) | —— |

三份文档同步:design 术语表新增三条并更新易混对照、§4/§12.1/§12.3 正文
改用新词;protocol §0.0b 与 ancard3s 字段说明同步;前端清单 §0.0 补「底」
字族对照表,§1.4、§2.6 相应改写。

口诀相应更新为:「亮牌」「余主公示」是统计类,「明牌」「开底」是牌面类。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 23:34:18 +08:00
joywayerandClaude Opus 5 6ec6bdbbc2 二七王:确立「余主公示」「底牌公示」术语,核查「亮牌」冲突
术语决策:
- A 庄家手牌达门槛后公开数量/结构 → 仍叫「亮牌」(保持不变)
- B 有人报无主后展示各家剩余主牌数与对数 → 新术语「余主公示」
- C 70 分坐庄摸底牌前向全场亮 8 张底牌 3 秒 → 新术语「底牌公示」
- 「亮主」确认为选主界面的标题文案、不是规则术语,三份文档均注明不得
  用它指代任何规则

「亮牌」冲突核查结论:无同名不同义,但有三处字面相近而语义方向相反的
读错风险——亮牌 vs 明牌(一个给数量、一个给牌面,方向还相反)、亮牌 vs
亮主(只差一字且同在庄家阶段)、亮牌 vs「亮出底牌」。故在 design 术语表、
协议 §0.0b、前端清单 §0.0 各加一张四类「公开信息」对照表,一句话口诀:
「亮牌」「余主公示」只给数字,「明牌」「底牌公示」才给牌面。

design.md:术语表新增 亮牌 / 余主公示 / 明牌 / 底牌公示 四条定义 +
易混对照表;§4、§9、§12.1 正文启用新术语。
packet_protocol.md:新增 §0.0b 术语小节;seatlist 第 5 位、ancard3s、
baozhu 三处字段说明冠以对应术语。
前端清单:§0.0 补对照表,§2.5 改名为「自己的主牌统计 + 余主公示」。

另据新图 等待庄家选主.png 抽出一个通用部件「状态提示条」(§2.8):
深色半透条 + 居中白字,等待类(等待庄家选主…)与即时反馈类(强甩失败)
共用同一套精灵,只切位置预设与文案。原单列的 BAR_SHUAICUO 并入该部件。

新增 T-32(该图底牌仍摊着与 design §4 的先后顺序对不上,需确认)、
T-33(等待类文案清单待定)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 23:04:03 +08:00
joywayerandClaude Opus 5 be06d3bf58 二七王:D-1..D-9 定稿落地,新增 S-6/S-7 两个服务端缺口
据新参考图(大局结算.png、强甩失败提示.png)与本轮确认,设计稿只剩 D-8:
- D-1 建房:子游戏只做「类别标签 + 选项」内容区,弹窗背景/标题/关闭/确认
  由平台提供,§5.8 相应不再列弹窗外壳。
- D-2 底牌 3 秒展示:复用庄家翻开底牌那套 UI,不要倒计时;非 70 分只有
  庄家可见、70 分全场可见——同一套界面只差可见范围,故砍掉原先规划的
  独立公示面板(资源 655 与精灵 1757/1758 作废)。
- D-3 甩错:桌面中央深色半透条「强甩失败」,不做收回动画。
- D-4 报无主:不做全场提示(资源 652、精灵 1753 作废),只让他家
  主N/对N 角标出现。
- D-6 明牌 / D-7 出牌历史:版式都照算奖牌型显示.png(遮罩 + 每家一排牌),
  与算奖叠加层共用同一套渲染。出牌历史【不按轮次】,按玩家聚合并用主牌序
  排序——前端摊平 pushlist 即可,无需改服务端。
- D-9 大局结算:玩家栏用精灵复制(新增 §5.7a)。

同时更正了一处我此前的理解错误:左侧那条「主牌对子: 1对」是【自己的主牌
统计、有手牌时常显】,不是 design §8.2 的庄家亮牌 liangpai。§2.5 已重写为
「自己的主牌统计 vs 他家的报无主角标」两套并列,并指出 liangpai 目前没有
界面位置(T-30)。

新增两个服务端缺口:
- S-6(阻塞 D-9):大局结算要显示「基础分/算奖分/傍王分」,而 account 只有
  累积得分与每局得分。傍王分尤其拆不出来——naward = 常规算奖 + 傍王王数,
  两者求和后才代入 grade_aw 公式,事后无法反推各自占比。建议把 N 拆成两个
  分量分别代入同一公式,相加应等于现 grade_aw(可作回归断言)。
- S-7(有泄露口子):明牌按钮条件收紧为「自己是无主玩家」,与 design §9.3
  的「全体三人」及服务端 have_baofu() 校验都不一致。前端先按严格规则显隐,
  但服务端校验未改前,有主牌的玩家伪造请求仍能拿到明牌数据。

另:修复文末附录丢失——三次提交前的一个切片脚本把它挤掉了,现已按当前
状态恢复并补充 SpriteCopyUtils 一行。新增 T-30/T-31、验收清单增至 17 条。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 21:32:38 +08:00
joywayerandClaude Opus 5 4aab71e6a3 二七王:实现 S-1 curmultiple 与 S-2 mustcard
S-1 当前抓分倍数(design §7.2.0):
- class.paiju.js 新增 get_curmultiple(),复用 get_qvalue + get_upgrade,
  与结算 aset.upgrade 同源同算法,差别仅在不含扣底(末轮才产生)。
- 随现有包下发、不另开推送:shangzhuang / chupai1-3 / deskinfo.PushCards。
- 三家同值,整表下发不涉及泄露(捡分本就公开);叫分未定时为 0。

S-2 跟牌必出牌(design §5.2):
- class.paiju.js 新增 get_mustcard(seat),内部复用与出牌校验完全相同的
  get_followcard,故建议与校验天然一致。
- 【只发给 nextseat 一家】:它是该玩家自己手牌的子集,整表下发会泄露
  他家手牌结构(server 红线:发全 ≠ 发多)。
- 四种情形不下发:非出牌阶段、该座位是本轮首家、首家甩牌、必出牌为空。
  甩牌一条尤其重要——甩牌跟牌走 flush_follow_ok 的逐分量匹配,与
  get_followcard 不是同一条路径,给建议会给错。

测试(新增 test/test_hint.js,33 checks):
- curmultiple:算法层逐档对齐 design §7.2.0 判定表(含爬坡 Q 分段)、
  三家同值、重连与出牌包一致、叫分未定为 0、与 get_upgrade 绝对值一致。
- mustcard:只有 nextseat 收到、另两家没有、内容确属收包者自己的牌、
  首家无、缺门有富余时无、手牌数恰等于首家张数时整手必出、甩牌不发、
  非出牌阶段不发、重连按 currseat 门控。

过程中修正的两个测试自身问题(均为脚本缺陷,非业务缺陷,业务判定经证据核实正确):
- 「缺门方无必出」原用例给闲2 只发 2 张牌,恰等于首家张数,get_followcard
  正确判定为「整手必出」;改为发 4 张才留出选择余地,并补一条对照用例
  锁住「恰等于张数则整手必出」这个正确行为。
- 「甩牌不发」「非出牌阶段不发」两条原本是假通过——桩的 startcount 仍是 -1,
  前置守卫先返回了 null,根本没测到被测因素。改为先把桩推进到跟牌态、
  每个用例只变动一个因素,并加一条「自证」用例确认推进后确实能算出必出牌。

另:mustcard 补进 test_leak.js 的 CARD_FIELDS 审计白名单(与 burycards 同理,
不加则误发不会被泄露审计发现)。反向验证:改成整表下发后,审计立刻报出
5 张越权牌;把 curmultiple 写死为 1 后,两条断言失败。均已还原、全绿。

协议文档同步 shangzhuang / chupai1-3 / PushCards 的字段说明。
至此 §7.5 服务端待补 5 条全部完成,服务端侧无遗留。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 20:25:32 +08:00
joywayerandClaude Opus 5 ecec44e93a 二七王:S-4 拆分 bottomcards 字段名,S-5 术语同步收口
S-4(服务端改动):底牌与埋牌底牌不再共用一个字段名。
- bottomcards = 底牌(发牌留桌 8 张)—— shangzhuang / ChooseMain / BuryCards,保持不变
- burycards   = 埋牌底牌(庄家埋下 8 张)—— maipai(mod.js)、PushCards(class.export.js)
- 结算 bottom.cards 未改名(分组名已表明是抠底相关),文档注明其语义

测试:
- test_leak.js 的 CARD_FIELDS 白名单加入 burycards。这一步是必须的——不加的话
  新字段会静默脱离下发面审计,闲家误收也发现不了。
- test_rpc.js 新增 11 条正反用例:庄家有 burycards 且为 8 张、庄家不再有
  bottomcards(抓改名漏改)、闲家两个字段皆无、重连 PushCards 同上。

验证(不止「跑过了」):
- 改动前基线 16 个文件全绿;改动后仍全绿,新增后 rpc 从 89 → 100 checks。
- 反向验证一:把 mod.js 的 burycards 改回 bottomcards → test_rpc 新用例 FAIL,
  且 test_leak 报出真实泄露(赋值改了而 delete 没改时,闲家会收到庄家埋的牌)。
- 反向验证二:删掉闲家侧的 delete msg.data.burycards → test_leak 立刻抓出
  5 张越权牌,确认 burycards 确已纳入审计白名单。
两次验证后均已还原并复跑全绿。

S-5:术语同步收口。design.md / packet_protocol.md / 代码注释均已改用
「底牌 / 埋牌底牌」;compliance 三份历史核对记录有意不改(反映当时的 design
表述,下轮核对按新 design 重做)。

另:T-9 亮牌条文案暂定「x对 x主」(取 liangpai.zhupair 与 zhu,其余档位
先不渲染、协议不变)。至此 29 项待确认规格全部有结论。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 18:07:17 +08:00