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:
@@ -8,6 +8,17 @@ const Quickbar = preload("res://ui/quickbar.gd")
|
||||
const UiAssets = preload("res://ui/ui_assets.gd")
|
||||
const UiManager = preload("res://ui/ui_manager.gd")
|
||||
|
||||
# __ReserveUseSkill 的最小 NetPlay 面:射程上下文 + 预约记录。
|
||||
class FakeRangeNetPlay extends Node:
|
||||
var distance_cm := 100000.0
|
||||
var reservations := []
|
||||
func skill_context() -> Dictionary:
|
||||
return {"bow_distance": 2, "target_distance": func(_vid: int) -> float: return distance_cm}
|
||||
func reserve_use_skill(vid: int, slot: int, range_cm: float) -> void:
|
||||
reservations.append([vid, slot, int(range_cm)])
|
||||
func is_use_skill_reserved(slot: int) -> bool:
|
||||
return not reservations.is_empty() and int(reservations[-1][1]) == slot
|
||||
|
||||
class FakeClient extends Node:
|
||||
signal skills_changed()
|
||||
signal skill_cooldown_end(skill: int)
|
||||
@@ -327,6 +338,37 @@ func _run() -> void:
|
||||
"mobile skill action assigns to first free quickbar slot")
|
||||
sk.close()
|
||||
|
||||
# 射程外(__ProcessEnemySkillTargetRange):不 cast、预约;蓄力技仅首次预约前发 use_skill(id, 0)
|
||||
if st.has(5) and st.is_charge_skill(5):
|
||||
var np := FakeRangeNetPlay.new()
|
||||
get_root().add_child(np)
|
||||
fc.skills.append({"id": 5, "level": 1, "master": 0})
|
||||
var qb3: Node = Quickbar.new()
|
||||
qb3.net_play = np
|
||||
get_root().add_child(qb3)
|
||||
qb3.setup(fc, st, ui, func() -> Node: return pl)
|
||||
qb3.assign(5, "skill", 5, false)
|
||||
var uses0: int = fc.skill_uses.size()
|
||||
var casts0: int = fc.casts.size()
|
||||
var rejected := []
|
||||
qb3.skill_rejected.connect(func(sid: int, code: String): rejected.append([sid, code]))
|
||||
qb3.activate(5)
|
||||
var range5: int = st.target_range(5) + 200
|
||||
_ck(fc.casts.size() == casts0 and fc.skill_uses.slice(uses0) == [[5, 0]],
|
||||
"charge skill out of range -> only use_skill(5, 0), no cast: %s" % [fc.skill_uses.slice(uses0)])
|
||||
_ck(np.reservations == [[2000, 5, range5]], "out of range -> reserve_use_skill(target, slot, TargetRange+bow*100)")
|
||||
_ck(rejected.is_empty(), "reservation is silent (no OnCannotUseSkill)")
|
||||
qb3.activate(5)
|
||||
_ck(fc.skill_uses.size() == uses0 + 1 and np.reservations.size() == 2,
|
||||
"already reserved charge skill -> no second use_skill(5, 0)")
|
||||
_ck(not qb3.activate_reserved(5), "reserved fire still out of range keeps the new reservation")
|
||||
np.distance_cm = 10.0
|
||||
_ck(qb3.activate_reserved(5), "reserved fire in range consumes the reservation")
|
||||
_ck(fc.casts.size() == casts0 + 1 and fc.skill_uses.back() == [5, 2000], "in range -> use_skill(5, target) + one cast")
|
||||
fc.skills.pop_back()
|
||||
qb3.free()
|
||||
np.free()
|
||||
|
||||
# 服务器 GC_QUICKSLOT_* 恢复:type 2 技能 / type 1 道具 / type 3 表情 -> 填格
|
||||
var qb2: Node = Quickbar.new()
|
||||
get_root().add_child(qb2)
|
||||
|
||||
Reference in New Issue
Block a user