Skip to content

0.3.0

Choose a tag to compare

@wm-develop wm-develop released this 03 Sep 08:04
· 36 commits to main since this release

好友德州 v0.3.0

服务端与客户端都需要更新,并且必须先执行数据库迁移。

发布方式

本 Release 不会直接附带 Web、Windows、Android、HarmonyOS 或 iOS 安装包,仅发布 Git 标签对应的源码快照。

客户端需要写入自建游戏服务地址,语音需要部署者自己的腾讯云 TRTC 配置,Android 与 HarmonyOS 正式分发还涉及各自的平台签名,因此各平台产物应由部署者根据自己的环境构建、签名和测试。

从空服务器开始部署,请参阅自建部署指南。已有部署的版本升级流程请参阅生产环境更新手册。

升级须知

  • 必须先迁移再启动服务:本次新增两组迁移 000008_room_join_lock 与 000009_client_version_gate。服务端启动时会校验迁移版本,未迁移会拒绝启动。
  • 服务端与客户端必须一起更新:本版修改了 WebSocket 关闭码语义(新增 4001 / 4002)、结算报文(payouts[].displayName)与底池分层规则。只更新一端会出现提示错乱或边池显示不一致。
  • 客户端版本门禁默认关闭,升级后行为不变。需要强制朋友更新时,管理员在客户端「服务器管理 → 最低客户端版本」填入版本号即可,不需要改环境变量或重建容器。
  • 上一版短暂引入过的 MINIMUM_CLIENT_VERSION 环境变量已移除,改存数据库。
  • 版本门禁对 v0.3.0 之前的客户端只能拒绝,无法提示。0.2.x 的客户端不认识这套机制:它们能登录(登录路径是管理员的逃生通道),但加入房间与连接牌桌都会被 426 拒绝,界面上只会显示「操作失败(client_too_old)」或反复重连。因此请先确认所有人都装好 0.3.0 再设置门槛;从 0.3.0 起,未更新的客户端才会看到干净的阻断页。
  • 设置门槛不会踢掉已经连在牌桌上的旧客户端——版本检查发生在 WebSocket 握手时,已建立的连接不回头重查,他们能把当前这手打完。

重要修复

房间读取全面失败(P0)

v0.2.1 之后的开发中,为房间增加 join_locked 列时,loadRoom 的 SELECT 语句带 r. 前缀未被批量替换命中,而 Scan 目标多了一个,导致所有房间读取以 expected 12 destination arguments in Scan, not 13 失败。表现为创建房间后立刻被踢回大厅提示创建失败,之后任何房间操作一律 internal_error。

单元测试走内存仓储发现不了,Postgres 集成测试需要 TEST_DATABASE_URL,未配置时跳过。现已把列清单与 Scan 目标放在一起,并增加不依赖数据库的列数守卫测试。

在线玩家被标为断线、自动准备失效、下注按钮卡死

试玩中最恶劣的一类问题,根因有三处,互相叠加:

  • 旧连接关闭把在线玩家标为断线。手机切换网络后新连接先加入,旧的死连接要等 TCP 超时才被察觉;此前它的关闭会无条件把玩家标为断线并在手间取消准备。服务端自动准备跳过断线者,客户端又只提交一次——这就是「倒计时走完却不准备」。现在服务端记录每个用户当前生效的连接,新连接加入时以关闭码 4001 主动关掉旧的;旧连接的关闭不再改动牌桌状态。
  • 牌局中途加入者结算后一律以断线入座。他在结算前没有引擎座位,加入时的 SetConnected 落空,入座时写死 connected=false 且此后无人置回。现按真实在线状态入座。
  • 动作被拒后要不回快照。动作因 stale_revision 被拒后客户端请求快照,序号已追平时服务端只回一个空的 replay.completed,客户端手里的 tableRevision 依旧过期,再点什么都被拒——表现为下注按钮怎么点都没反应。现在显式请求一律返回完整快照。

客户端另加三道保险:system.error 也视为动作未生效并放开按钮、动作回执 5 秒看门狗、自动准备 3 秒后重试。发牌动画改用单调时钟,避免手机校时回拨导致动画永远播不完、按钮一直禁用。

无人 all in 时不该出现边池

边池的意义是「部分玩家无权争夺」,只有 all in 造成投入档位差异时才会产生。此前按投入额分层,弃牌者投得少同样会切出一层,而那一层与下一层的候选人集合完全相同——于是没人 all in 的牌局里也会显示「边池 1」。

现在候选人相同的相邻层合并为一个池:同一批人比同样的牌,赢家必然相同,合并后余数也只分一次,与真实牌桌一致。客户端相应地在只有一个池时称「底池」,有边池时才区分「主池 / 边池 N」。

结算文案显示用户 ID、底池金额对不上

同一个根因:赢家常常赢完这手就立刻离开房间。此后房间成员表和引擎座位里都没有他,靠座位反查昵称只能退化成显示 usr_...,而 totalPot(按在座玩家投入累加)也会小于结算金额。

现在服务端在结算时固化昵称并随 potAwards 下发;结算展示期间 totalPot 改用各池金额之和——发两次时每个池均分到各块牌面,所有 award 之和仍等于本手总底池。

被踢出房间提示「操作失败 permission_denied」

此前服务端用通用的 policy violation 关闭连接,客户端只能靠随后重连时 table.join 被拒来推断。现在用专门的关闭码 4002,关闭原因为 removed_by_owner 或 removed_by_administrator,客户端据此不再重连,回到大厅弹窗说明是谁移出的、筹码已退回钱包。

HarmonyOS:语音与提示音并存

开麦后过几秒就听不到其他玩家说话,系统音量条从话筒(通话音量)变为喇叭(媒体音量)。排查走了四轮,前三轮的方案都被真机否定,记录在此供参考:

尝试 结果
改用音频通话场景、不切换角色 无效
语音进行中放弃提示音 有效,但鸿蒙语音里没有提示音
交给 TRTC startPlayMusic 走通话流 不掐语音,但音量偏小、有延迟断续——那条链路是给背景音乐用的
并发模式改为 CONCURRENCY_MIX_WITH_OTHERS 无效

真因是牌局提示音,不是 TRTC。鸿蒙的音频焦点是应用级的:audioplayers 每次播放前调用 setAudioSessionScene(AUDIO_SESSION_SCENE_MEDIA) 并以 CONCURRENCY_DEFAULT 激活音频会话,该模式独占物理输出通道并压制应用内其他音频流。第四轮证明改并发模式无效,说明致命的是会话切换本身。

最终改为鸿蒙原生 SoundPool 通道:系统专为短音效提供的低时延通道,完全不经过 AudioSessionManager。已真机验证通过,语音与提示音并存,音量和及时性正常。

四种应对方式保留为 HarmonyVoiceSoundStrategy,每种的真机结论写在枚举注释里,改一个默认值即可切换对比。

新增功能

本房间战绩与换算

牌桌顶部新增按钮,显示本人在当前房间的净胜负,并按自定比例(默认 10 元 = 2000 筹码)换算金额。

净胜负 = 离桌返还 + 桌上筹码 − 累计带入。把桌上筹码计入是因为玩家通常在牌局中途查看,此时盈亏还没有通过离桌返还落回钱包。换算只在客户端本地进行,服务端不存储也不传输任何金额,产品仍不接入支付。

房主的房间管理

设置面板内新增「房间管理」,只对房主显示:

  • 移出玩家:复用玩家自己离桌的同一条路径,筹码照常退回钱包。只在手间允许——牌局进行中把人踢走会牵扯底池归属与行动顺序。管理员不可被房主移出:房主是房间内的角色,管理员是服务器级角色,需要能进入任何房间处理纠纷。
  • 关闭房间入口:只挡新加入者,房内成员不受影响。

客户端版本门禁

开发期服务端改动频繁,旧客户端连上新服务端会出各种难以定位的问题,而参与试玩的朋友常常忘记更新。

  • 最低版本存在数据库,管理员在「服务器管理」页调整,立即生效且不需要改环境变量或重建容器——只更新客户端的发布不必登服务器。
  • 版本号统一为 major*1000000 + minor*1000 + patch 编码(0.2.1 → 2001),四端与鸿蒙 versionCode 同一套。此前 pubspec 的 +3 让 Android 的 versionCode 是 3 而鸿蒙是 2001,两套编码无法比较。
  • 客户端每次请求带 X-Client-Version(WebSocket 走查询参数,浏览器不允许设自定义头)。门禁包在整个 mux 外层,WebSocket 升级一并覆盖——只挡登录接口的话,已经登录的旧客户端还能继续连牌桌。
  • 过旧返回 426,客户端进入不可跳过的阻断页。Web 端提示刷新页面而不是重新安装。
  • 登录、刷新令牌与「调整门槛」三条路径豁免门禁,作为逃生通道:否则门槛设得比管理员自己的客户端还高时会把自己锁死。
  • 新增 dart tool/set_version.dart <版本号>,一次改完三处版本号并拦住版本倒退。

界面调整

  • 牌桌吃满可用宽度:此前被固定上限 1040 卡住,平板与宽窗口可用宽度有 1300 以上,左侧白白空出一大片。改为按 1.95 的宽高比约束,只有超宽画布才会被限制。2~10 人的座位互不重叠、不遮挡公共牌区、不侵入两栏的测试全部在新宽度下重跑通过。
  • 平板横屏右栏按钮被裁掉且点不动:右栏高度不足时下注区被直接裁掉,改为贴底可滚动;系统手势导航条压在屏幕底部、那片区域触摸归系统,原生避让区查询在挖孔之外补上导航条(HarmonyOS TYPE_NAVIGATION_INDICATOR、Android navigationBars()),Flutter 侧再用 viewPadding/systemGestureInsets 兜底。
  • 顶部控件让开屏幕圆角:安卓手机上右上角的聊天按钮被屏幕圆角切掉一角。圆角既不属于挖孔也不属于系统栏,两套 inset 都不包含它,只能单独问系统要——Android 用 WindowInsets.getRoundedCorner(API 31+)上报四角半径,顶部两栏据此下移,已被安全区推开的那部分不重复计。选择下移而不是左移,是因为左移会挤到玩家框。鸿蒙没有对应接口,实测也正常。
  • HarmonyOS 平板显式全屏:应用此前一直顶着系统状态栏。代码注释称「FlutterAbility 管理全屏」,实际上并没有——鸿蒙手机恰好默认全屏才没暴露。现在在 onWindowStageCreate 里做沉浸式布局并隐藏状态栏。只隐藏状态栏、保留手势导航指示器:把它一起隐藏后系统仍占着屏幕底部那条手势区,但避让区接口会返回 0,界面不再让开,落在那里的按钮又会变得点不动。
  • 聊天入口按端分层:手机保持右栏里的独立大按钮;大屏做成信息栏右上角的大图标,不与那排小按钮并列,省出的竖向空间留给下注区。
  • 手机三个大下注按钮由 44 加高到 56,试玩反馈原高度容易误触。
  • 注码尺度按钮去掉「全下」:它不是「几分之几底池」那一类的尺度,混在里面容易误触。要全下把滑块推到最右或直接输入额度即可。
  • 大屏的房间信息与语音控件并入右栏顶部:玩家框现在会伸出桌沿,5~7 人时左右上角的座位会压到原先放在画布四角的控件。
  • 设置面板限高并可滚动:手机横屏可用高度很小,新增条目后一屏放不下。

已知限制

  • 仍是单游戏服务实例,进程异常时该实例上进行中的那一手会作废。
  • 24 小时稳定性观测与弱网验收的工具和清单已就绪,但仍未实跑。
  • 平板的系统手势导航条让开、屏幕圆角让开与 HarmonyOS 全屏尚未在真机确认。三者的实现依据都是各平台的系统接口(避让区、getRoundedCorner、window 全屏),本地无法验证。
  • HarmonyOS 原生 SoundPool 通道与 EntryAbility 的代码无法在本机编译验证(无 DevEco 工具链)。SoundPool 已真机验证运行行为正常,全屏改动尚未验证。
  • 本次发现的 P0(房间读取列数错配)说明:改动 *postgres_repository.go 时,仅靠内存仓储的单元测试不足以发现 SQL 与 Scan 的错配。有条件时应配置 TEST_DATABASE_URL 跑集成测试。
  • iOS 不在验收范围。