Files
mtgodot-poc/project/playable_config_gate.gd
shenleiandClaude Opus 5 f39a55fdd5 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
2026-09-11 22:06:48 +09:00

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