feat(playable): 首个 Mac 联网内测版 STB-01 soak 工具与发布状态文档

- 新增 script/playable_soak.sh:先做 soak 配置/资源校验,无 --allow-gameplay
  只校验不启动客户端;N(>=10) 次独立正常退出运行 + 墙钟 soak,失败即停止后续
  运行并记 BLOCKED;写本批次 release-manifest.json 后聚合
- run_client_gate.sh:确认的故障代理对所有 suite 启动(共享场景的退出运行也经代理);
  soak 超时下限只约束 soak 客户端(validate_soak 增加 soak_client 参数)
- 新增本地 127.0.0.1 故障代理、RSS 采样/内存判定、窗口/指标/流程模块及其测试
- forest_mob_render_test 输出 PASS/FAIL 标记,供 rendering_batch_test.sh 识别
- 新增 docs/FIRST-MAC-PLAYABLE-STATUS.md:如实记录 PASS/BLOCKED、已知问题与环境需求
- .gitignore 排除本地场景配置、凭据文件与运行输出

离线回归:rendering_batch_test.sh failures=0,playable_gate_test.sh PASS,
node 夹具测试 PASS。未联网运行,未重建候选包。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
This commit is contained in:
shenlei
2026-09-11 22:06:48 +09:00
co-authored by Claude Opus 5
parent 374d4165d8
commit f39a55fdd5
66 changed files with 9189 additions and 951 deletions
+27 -5
View File
@@ -8,7 +8,10 @@
# ./script/live_smoke_test.sh --no-reconnect
#
# 账号和密码优先从环境变量读取;未提供时安全地交互输入,不写入脚本。
# 服务器地址与客户端一样经 project/net/serverinfo.gd 解析,不在脚本里写死。
# 不要在本脚本中启用 set -x。
set -euo pipefail
set +x
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO="$(cd "$SCRIPT_DIR/.." && pwd)"
@@ -37,6 +40,7 @@ usage() {
环境变量:
MT_ACCOUNT / MT_PASSWORD 可预先提供账号密码,未提供时交互输入
MT_CHAR_SLOT=N 指定角色槽位;不设置时自动选择第一个角色
MT_GODOT 解析 serverinfo 用的 godot,默认 godot
EOF
}
@@ -61,7 +65,8 @@ if [ -f "$CONFIG_FILE" ]; then
fi
if [ -z "${MT_ACCOUNT:-}" ]; then
read -r -p "账号: " MT_ACCOUNT
read -r -s -p "账号: " MT_ACCOUNT
echo
fi
if [ -z "${MT_PASSWORD:-}" ]; then
read -r -s -p "密码: " MT_PASSWORD
@@ -85,9 +90,19 @@ if [ ! -x "$EXECUTABLE" ]; then
fi
echo "== check server ports =="
for port in 11000 13002; do
if ! nc -G 3 -z 192.168.21.203 "$port" >/dev/null 2>&1; then
echo "服务器端口不可达:192.168.21.203:$port" >&2
# 登录页默认 server 0 / 第一个频道;与 AppFlow 同一 ServerInfo 解析。
ADDRESS_LINE="$("${MT_GODOT:-godot}" --headless --path project --script print_server_address.gd 2>/dev/null | grep '^SERVER_ADDRESS ' || true)"
if [ -z "$ADDRESS_LINE" ]; then
echo "无法从 serverinfo 解析服务器地址" >&2
exit 2
fi
read -r AUTH_HOST AUTH_PORT GAME_HOST GAME_PORT <<<"$(node -e '
const a = JSON.parse(process.argv[1].slice("SERVER_ADDRESS ".length));
process.stdout.write([a.auth_host, a.auth_port, a.game_host, a.game_port].join(" "));
' "$ADDRESS_LINE")"
for endpoint in "$AUTH_HOST:$AUTH_PORT" "$GAME_HOST:$GAME_PORT"; do
if ! nc -G 3 -z "${endpoint%:*}" "${endpoint##*:}" >/dev/null 2>&1; then
echo "服务器端口不可达:$endpoint" >&2
exit 3
fi
done
@@ -111,7 +126,8 @@ echo "== run live smoke =="
echo "log: $LOG"
echo "report: $REPORT"
set +e
"$EXECUTABLE" 2>&1 | tee "$LOG"
# 日志落盘前脱敏;PIPESTATUS[0] 仍是 APP 的真实退出码。
env -u MT_ASSETS "$EXECUTABLE" 2>&1 | node script/redact_stream.mjs | tee "$LOG"
APP_STATUS=${PIPESTATUS[0]}
set -e
@@ -140,6 +156,12 @@ if [ "$APP_STATUS" -ne 0 ]; then
exit "$APP_STATUS"
fi
# 与 package_render_test.sh / validate_playable_report.mjs 相同的完整日志门禁。
if grep -nE 'SCRIPT ERROR|Parse Error|^ERROR:|leaked at exit|shaders of type .* were never freed' "$LOG"; then
echo "LIVE_SMOKE RESULT: FAIL (log exit gate)" >&2
exit 1
fi
if [ "$REPORT_STATUS" != "PASS" ]; then
echo "LIVE_SMOKE RESULT: FAIL (report status=$REPORT_STATUS)" >&2
exit 1