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
+54
View File
@@ -0,0 +1,54 @@
#!/bin/sh
# Stand-in for the packaged client used by script/playable_gate_test.sh.
# The fake app's Mach-O execs this file, so it runs as the runner's own child
# PID. It never contacts a server; MT_FAKE_SCENARIO picks the behaviour.
scenario="${MT_FAKE_SCENARIO:-pass}"
write_report() {
# $1 = PASS|BLOCKED, $2 = drop-last|evidence-missing|wrong-run|""
node -e '
const fs = require("node:fs");
const path = require("node:path");
const [status, variant] = process.argv.slice(1);
const out = process.env.MT_TEST_OUTPUT;
const required = JSON.parse(fs.readFileSync(path.join(out, "required-cases.json"), "utf8")).cases;
const runId = variant === "wrong-run" ? "previous-run-0000" : process.env.MT_PLAYABLE_RUN_ID;
const ids = variant === "drop-last" ? required.slice(0, -1) : required;
const cases = ids.map((id) => ({ id, status, duration_ms: 1, reason: status === "PASS" ? "fake" : "fixture_not_ready",
evidence: variant === "evidence-missing" && id === "NET-LOGIN-01" ? ["net-login-01.png"] : [] }));
const events = ids.map((id, i) => JSON.stringify({ monotonic_us: 1000 + i, run_id: runId, case_id: id, connection_epoch: 1,
stage: "LOGIN", kind: "phase_changed", actor_vid: 0, target_vid: 0, payload: { source: "server" } }));
fs.writeFileSync(process.env.MT_TEST_EVENTS, events.join("\n") + "\n");
const report = {
schema_version: 1, run_id: runId, suite: process.env.MT_PLAYABLE_SUITE || process.env.MT_TEST_MODE, status,
build: { engine_sha256: process.env.MT_BUILD_ENGINE_SHA256, extension_sha256: process.env.MT_BUILD_EXTENSION_SHA256,
pck_sha256: process.env.MT_BUILD_PCK_SHA256, arch: process.env.MT_BUILD_ARCH },
environment: { os: "macOS", renderer: "fake", resolution: [1280, 720] },
cases, required_cases: ids, events: "events.jsonl", event_count: events.length, failures: [],
blocked: status === "BLOCKED" ? ids.map((id) => `${id}: fixture_not_ready`) : [],
coverage: { required: cases.length, passed: status === "PASS" ? cases.length : 0 },
exit_gate: { checked: false, process_code: null, errors: [] },
};
fs.writeFileSync(process.env.MT_TEST_REPORT, JSON.stringify(report, null, 2));
' "$1" "${2:-}"
}
case "$scenario" in
pass) write_report PASS; echo "PLAYABLE CLIENT: PASS" ;;
soak-pass) write_report PASS; if [ "${MT_PLAYABLE_SUITE:-}" = "soak" ]; then sleep 3; fi; echo "PLAYABLE CLIENT: PASS" ;;
exit3) write_report PASS; echo "PLAYABLE CLIENT: PASS"; exit 3 ;;
signal) write_report PASS; kill -KILL $$ ;;
hang) trap '' TERM; echo "ignoring TERM"; while :; do sleep 1; done ;;
stale) write_report PASS wrong-run ;;
no-report) echo "exited before writing a report" ;;
rid) write_report PASS; echo "WARNING: 3 shaders of type CanvasItem were never freed." ;;
leak) write_report PASS; echo "WARNING: ObjectDB instances leaked at exit (run with --verbose for details)." ;;
missing-case) write_report PASS drop-last ;;
missing-evidence) write_report PASS evidence-missing ;;
blocked) write_report BLOCKED ;;
print-secret) write_report PASS; echo "login request for $MT_ACCOUNT with $MT_PASSWORD" ;;
fifo-holder) write_report PASS; sleep 13 & exit 0 ;;
no-creds) if [ -n "${MT_ACCOUNT:-}${MT_PASSWORD:-}" ]; then echo "SCRIPT ERROR: credentials reached an offline child"; fi; write_report PASS ;;
*) echo "unknown fake scenario" >&2; exit 64 ;;
esac
exit 0