Files
mtgodot-poc/project/app_flow_lifecycle_test.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

99 lines
3.6 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# app_flow_lifecycle_test —— §9.3 生命周期单一属主回归。
# AppFlow 跨登录 / 选人 / 游戏持有唯一 AppLifecycleGameScene 不重复创建。
extends SceneTree
const AppFlow = preload("res://app_flow.gd")
class FakeClient extends Node:
signal phase_changed(phase: String)
signal disconnected(reason: String)
signal char_list(characters: Array)
signal char_name_changed(pid: int, name: String)
signal entered_game()
signal login_failed(reason: String)
signal char_created(slot: int)
signal char_create_failed(reason: int)
signal char_deleted(slot: int)
signal char_delete_failed()
var selects := []
var reconnects := 0
func select_character(index: int) -> bool:
selects.append(index)
return true
func reconnect() -> bool:
reconnects += 1
return true
var _fail := 0
func _ck(ok: bool, message: String) -> void:
if not ok:
_fail += 1
printerr("FAIL: " + message)
func _init() -> void:
var client := FakeClient.new()
var flow := AppFlow.new()
flow.build_game_scene = false
get_root().add_child(flow)
flow.start("", client)
await process_frame
var lifecycle_nodes: Array[Node] = []
for child in flow.get_children():
if child.name == "AppLifecycle":
lifecycle_nodes.append(child)
_ck(lifecycle_nodes.size() == 1, "AppFlow owns exactly one AppLifecycle")
_ck(flow.get_node_or_null("AppLifecycle") != null,
"AppLifecycle survives login screen construction")
_ck(client.get_parent() == flow,
"AppFlow keeps the same client beside lifecycle coordinator")
var login_snapshot: Dictionary = flow.get_playable_snapshot()
_ck(login_snapshot.get("stage", "") == "LOGIN" and not login_snapshot.get("scene_ready", true),
"playable snapshot is a read-only LOGIN summary")
# A live game keeps its scene during a transient disconnect. ReconnectUI
# owns the retry while the next character list silently re-enters the last
# selected slot instead of flashing the character-select page.
flow._game = Node.new()
flow.add_child(flow._game)
flow._state = AppFlow.GAME
flow._selected_char_slot = 2
client.disconnected.emit("wifi lost")
_ck(flow.state() == AppFlow.GAME and flow._reconnecting_game,
"game disconnect enters reconnecting state without leaving GAME")
_ck(flow._reconnect != null and flow._reconnect.is_showing(),
"game disconnect shows reconnect overlay")
client.char_list.emit([{"index": 2, "name": "Hero"}])
_ck(client.selects == [2], "reconnect re-enters remembered character slot")
client.entered_game.emit()
_ck(flow.state() == AppFlow.GAME and not flow._reconnecting_game
and not flow._reconnect.is_showing(),
"successful reconnect restores GAME and hides overlay")
var game_snapshot: Dictionary = flow.get_playable_snapshot()
_ck(game_snapshot.get("stage", "") == "GAME" and not game_snapshot.get("reconnecting", true),
"playable snapshot reports GAME after reconnect")
# Auto-login with an explicit MT_CHAR_SLOT must never fall back to another
# character, and the chosen slot must be remembered for a later reconnect.
flow._state = AppFlow.LOGIN
flow.auto_login = true
flow._auto_char_slot = 5
flow._selected_char_slot = -1
client.selects.clear()
client.char_list.emit([{"index": 0, "name": "Other"}])
_ck(client.selects.is_empty(), "absent explicit slot does not enter chars[0]")
flow._state = AppFlow.LOGIN
client.char_list.emit([{"index": 0, "name": "Other"}, {"index": 5, "name": "Fixture"}])
_ck(client.selects == [5] and flow._selected_char_slot == 5,
"explicit slot is entered and remembered for reconnect")
flow.queue_free()
await process_frame
if _fail == 0:
print("PASS: app_flow_lifecycle_test (single lifecycle owner)")
quit(0)
else:
printerr("%d check(s) failed" % _fail)
quit(1)