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:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"engine_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"extension_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"pck_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"arch": "arm64"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"monotonic_us":1000,"run_id":"fixture-run","case_id":"FIXTURE-01","connection_epoch":1,"stage":"LOGIN","kind":"phase_changed","actor_vid":0,"target_vid":0,"payload":{"source":"server"}}
|
||||
Executable
+54
@@ -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
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"maps": {
|
||||
"<map_key from map-assets.json>": {
|
||||
"races": [],
|
||||
"viewpoints": [
|
||||
{
|
||||
"id": "flat-01",
|
||||
"kind": "flat",
|
||||
"status": "unconfirmed",
|
||||
"server_cm": [0, 0],
|
||||
"camera_yaw_deg": 45
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"notes": "Copy to test/playable/forest-viewpoints.<name>.local.json (ignored). kind is flat|slope|dense|warp; server_cm is the confirmed in-game global position; status becomes confirmed only with confirmed_by set to the confirming role (not a person's name). races lists server-confirmed mob vnums for the map; empty means the 12 candidate tree monsters are placed as candidates only. Never add credentials here."
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-blocked",
|
||||
"run_id": "fixture-run",
|
||||
"suite": "playable",
|
||||
"status": "BLOCKED",
|
||||
"build": {
|
||||
@@ -9,11 +9,39 @@
|
||||
"pck_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"arch": "arm64"
|
||||
},
|
||||
"environment": {"os": "macOS", "renderer": "Metal", "resolution": [1280, 720]},
|
||||
"cases": [{"id": "FIXTURE-01", "status": "BLOCKED", "duration_ms": 0, "reason": "fixture_not_ready", "evidence": []}],
|
||||
"environment": {
|
||||
"os": "macOS",
|
||||
"renderer": "Metal",
|
||||
"resolution": [
|
||||
1280,
|
||||
720
|
||||
]
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"id": "FIXTURE-01",
|
||||
"status": "BLOCKED",
|
||||
"duration_ms": 0,
|
||||
"reason": "fixture_not_ready",
|
||||
"evidence": []
|
||||
}
|
||||
],
|
||||
"required_cases": [
|
||||
"FIXTURE-01"
|
||||
],
|
||||
"events": "events.jsonl",
|
||||
"event_count": 1,
|
||||
"failures": [],
|
||||
"blocked": ["fixture_not_ready"],
|
||||
"coverage": {"required": 1, "passed": 0},
|
||||
"exit_gate": {"checked": false, "process_code": null, "errors": []}
|
||||
"blocked": [
|
||||
"FIXTURE-01: fixture_not_ready"
|
||||
],
|
||||
"coverage": {
|
||||
"required": 1,
|
||||
"passed": 0
|
||||
},
|
||||
"exit_gate": {
|
||||
"checked": false,
|
||||
"process_code": null,
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"run_id": "fixture-valid",
|
||||
"run_id": "fixture-run",
|
||||
"suite": "playable",
|
||||
"status": "PASS",
|
||||
"build": {
|
||||
@@ -9,11 +9,37 @@
|
||||
"pck_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"arch": "arm64"
|
||||
},
|
||||
"environment": {"os": "macOS", "renderer": "Metal", "resolution": [1280, 720]},
|
||||
"cases": [{"id": "FIXTURE-01", "status": "PASS", "duration_ms": 1, "reason": "ok", "evidence": []}],
|
||||
"environment": {
|
||||
"os": "macOS",
|
||||
"renderer": "Metal",
|
||||
"resolution": [
|
||||
1280,
|
||||
720
|
||||
]
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"id": "FIXTURE-01",
|
||||
"status": "PASS",
|
||||
"duration_ms": 1,
|
||||
"reason": "ok",
|
||||
"evidence": []
|
||||
}
|
||||
],
|
||||
"required_cases": [
|
||||
"FIXTURE-01"
|
||||
],
|
||||
"events": "events.jsonl",
|
||||
"event_count": 1,
|
||||
"failures": [],
|
||||
"blocked": [],
|
||||
"coverage": {"required": 1, "passed": 1},
|
||||
"exit_gate": {"checked": false, "process_code": null, "errors": []}
|
||||
"coverage": {
|
||||
"required": 1,
|
||||
"passed": 1
|
||||
},
|
||||
"exit_gate": {
|
||||
"checked": false,
|
||||
"process_code": null,
|
||||
"errors": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"suite": "playable",
|
||||
"cases": [
|
||||
"FIXTURE-01"
|
||||
]
|
||||
}
|
||||
@@ -4,7 +4,11 @@
|
||||
"protocol": "classic",
|
||||
"server": {
|
||||
"server_index": 0,
|
||||
"channel_index": 0
|
||||
"channel": 1,
|
||||
"auth_host": "",
|
||||
"auth_port": 0,
|
||||
"game_host": "",
|
||||
"game_port": 0
|
||||
},
|
||||
"character_slot": 0,
|
||||
"map_key": "",
|
||||
@@ -14,6 +18,26 @@
|
||||
"skill_cases": [],
|
||||
"resolution": [1280, 720],
|
||||
"loops": 3,
|
||||
"timeout_seconds": 120,
|
||||
"notes": "Copy to a local ignored file and fill from the confirmed server fixture. Never add credentials here."
|
||||
"timeout_seconds": 900,
|
||||
"soak": {
|
||||
"duration_seconds": 7200,
|
||||
"rest_seconds": 30,
|
||||
"warmup_rounds": 1,
|
||||
"min_rounds": 10,
|
||||
"reconnects": 10,
|
||||
"exits": 10,
|
||||
"resolutions": {
|
||||
"sizes": [[1280, 720], [1440, 900], [1600, 1000]],
|
||||
"switches_per_size": 10
|
||||
},
|
||||
"warp": {
|
||||
"status": "unconfirmed",
|
||||
"notes": "Set status=confirmed only after the environment owner verifies a legal portal: required (even, >=20 out-and-back legs), portal_cm, destination_map_key, return_portal_cm, cross_server."
|
||||
},
|
||||
"faults": {
|
||||
"status": "unconfirmed",
|
||||
"notes": "Set status=confirmed only with a confirmed 127.0.0.1 test proxy (mode local_proxy, per_type, unreachable_seconds 1..20, upstream {auth_host,auth_port,game_host,game_port} = the real test server). server.* and the serverlist entry must then be 127.0.0.1 proxy ports; script/run_client_gate.sh starts script/playable_fault_proxy.mjs for this client only. A server warp that hands out a direct game address bypasses the proxy. Never change global routes or firewall; active reconnect is not a substitute."
|
||||
}
|
||||
},
|
||||
"notes": "Copy to test/playable/<name>.local.json (ignored) and fill from the confirmed server fixture: server.* must equal the serverinfo entry for server_index/channel; map_key is the asset directory holding Setting.txt; waypoints are server cm. Never add credentials here. The soak suite (STB-01) additionally needs timeout_seconds >= soak.duration_seconds + 900."
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"notes": [
|
||||
"每职业从测试角色真实已学技能中选择:single_target 单体、area 范围、self_buff 自身增益、flying 飞行效果。",
|
||||
"status=unconfirmed:尚未由测试环境负责人确认,对应 CBT 用例为 BLOCKED,不得发布。",
|
||||
"status=confirmed:填 skill_id(该职业已学、非被动)、target(enemy|self|ground)、repeats(>=10)、required_evidence(至少一个服务端证据)。",
|
||||
"status=not_applicable:该职业确无此类技能,必须填 checklist_ref 指向发布清单中的确认记录。",
|
||||
"脚本和测试从不补技能 ID;只改本地副本 test/playable/skill-cases.local.json 时同样适用。检查:godot --headless --path project --script playable_skill_matrix.gd -- --job WARRIOR"
|
||||
],
|
||||
"jobs": {
|
||||
"WARRIOR": {
|
||||
"single_target": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"area": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"self_buff": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"flying": {
|
||||
"status": "unconfirmed"
|
||||
}
|
||||
},
|
||||
"ASSASSIN": {
|
||||
"single_target": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"area": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"self_buff": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"flying": {
|
||||
"status": "unconfirmed"
|
||||
}
|
||||
},
|
||||
"SURA": {
|
||||
"single_target": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"area": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"self_buff": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"flying": {
|
||||
"status": "unconfirmed"
|
||||
}
|
||||
},
|
||||
"SHAMAN": {
|
||||
"single_target": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"area": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"self_buff": {
|
||||
"status": "unconfirmed"
|
||||
},
|
||||
"flying": {
|
||||
"status": "unconfirmed"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user