- 新增 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
76 lines
3.1 KiB
GDScript
76 lines
3.1 KiB
GDScript
extends SceneTree
|
|
|
|
## Parent-runner precondition gate. Runs headless before the packaged client
|
|
## starts; it never opens a network connection or reads credentials.
|
|
##
|
|
## env:
|
|
## MT_PLAYABLE_VALIDATE_CONFIG scenario JSON (required)
|
|
## MT_PLAYABLE_SUITE playable|full|soak (default playable)
|
|
## MT_PLAYABLE_ASSETS asset root used for map/waypoint checks
|
|
## MT_PLAYABLE_SERVERLIST test-mode serverlist override (same value is
|
|
## handed to the client, so both resolve alike)
|
|
## MT_PLAYABLE_GATE_OUTPUT directory for required-cases.json / server-address.json
|
|
## exit: 0 = ready, 2 = configuration/precondition blocked.
|
|
|
|
const Config = preload("res://testing/playable_config.gd")
|
|
const ServerInfoRes = preload("res://net/serverinfo.gd")
|
|
|
|
func _init() -> void:
|
|
var path := OS.get_environment("MT_PLAYABLE_VALIDATE_CONFIG")
|
|
var suite := OS.get_environment("MT_PLAYABLE_SUITE").strip_edges()
|
|
if suite.is_empty():
|
|
suite = "playable"
|
|
var errors: Array = []
|
|
if not (suite in Config.SUITES):
|
|
errors.append("unknown suite: %s" % suite)
|
|
var result := Config.load_file(path)
|
|
errors.append_array(result.errors)
|
|
# A present soak block is validated for every suite: its confirmed fault proxy also
|
|
# fronts the playable exit runs that share the scenario.
|
|
if result.ok and (suite == "soak" or result.config.has("soak")):
|
|
errors.append_array(Config.validate_soak(result.config, suite == "soak").errors)
|
|
var address := {}
|
|
var bounds := {}
|
|
if result.ok:
|
|
var serverinfo := ServerInfoRes.new()
|
|
var override := OS.get_environment("MT_PLAYABLE_SERVERLIST").strip_edges()
|
|
if not override.is_empty():
|
|
if not serverinfo.load_file(override):
|
|
errors.append("MT_PLAYABLE_SERVERLIST cannot be loaded")
|
|
else:
|
|
serverinfo.load_file("res://serverlist.txt")
|
|
var resolved := Config.resolve_server(result.config, serverinfo)
|
|
errors.append_array(resolved.errors)
|
|
address = resolved.address
|
|
var assets := OS.get_environment("MT_PLAYABLE_ASSETS").strip_edges()
|
|
if assets.is_empty():
|
|
errors.append("MT_PLAYABLE_ASSETS is not set; resource preconditions cannot be checked")
|
|
else:
|
|
var resources := Config.check_resources(result.config, assets)
|
|
errors.append_array(resources.errors)
|
|
bounds = resources.bounds
|
|
if not errors.is_empty():
|
|
for error in errors:
|
|
printerr("CONFIG: " + String(error))
|
|
quit(2)
|
|
return
|
|
var out_dir := OS.get_environment("MT_PLAYABLE_GATE_OUTPUT").strip_edges()
|
|
if not out_dir.is_empty():
|
|
var cases := Config.required_cases(result.config, suite)
|
|
if not _write_json(out_dir.path_join("required-cases.json"), {"schema_version": 1, "suite": suite, "cases": cases}) \
|
|
or not _write_json(out_dir.path_join("server-address.json"), address) \
|
|
or not _write_json(out_dir.path_join("map-bounds.json"), bounds):
|
|
printerr("CONFIG: cannot write gate output")
|
|
quit(2)
|
|
return
|
|
print("PLAYABLE CONFIG: PASS")
|
|
quit(0)
|
|
|
|
func _write_json(path: String, value: Variant) -> bool:
|
|
var file := FileAccess.open(path, FileAccess.WRITE)
|
|
if file == null:
|
|
return false
|
|
file.store_string(JSON.stringify(value, " ") + "\n")
|
|
file.close()
|
|
return true
|