- 新增 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
66 lines
3.0 KiB
GDScript
66 lines
3.0 KiB
GDScript
extends RefCounted
|
|
|
|
## STB-RESIZE-01 的真实窗口适配器(PlayableFlow.window)。
|
|
##
|
|
## macOS Retina 下 DisplayServer 的窗口尺寸是物理像素,逻辑尺寸 = 物理像素 / 屏幕缩放。
|
|
## 流程只按逻辑尺寸判定;物理像素、缩放和视口尺寸一并写进事件,Retina 的 2x 不是分辨率错误。
|
|
## 请求尺寸超出当前屏幕可用区域(含标题栏)时如实拒绝(request_logical_size 返回 false),
|
|
## 由流程记为 BLOCKED,不缩小目标冒充通过。headless / 全屏 / 最大化窗口不可调整。
|
|
|
|
var _viewport: Viewport
|
|
|
|
func _init(viewport: Viewport = null) -> void:
|
|
_viewport = viewport
|
|
|
|
func availability() -> Dictionary:
|
|
if DisplayServer.get_name() == "headless":
|
|
return {"ok": false, "reason": "headless display server has no window"}
|
|
match DisplayServer.window_get_mode():
|
|
DisplayServer.WINDOW_MODE_FULLSCREEN, DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
|
|
return {"ok": false, "reason": "window is fullscreen; logical size switching needs windowed mode"}
|
|
DisplayServer.WINDOW_MODE_MAXIMIZED:
|
|
return {"ok": false, "reason": "window is maximized; logical size switching needs windowed mode"}
|
|
DisplayServer.WINDOW_MODE_MINIMIZED:
|
|
return {"ok": false, "reason": "window is minimized"}
|
|
if screen_scale() <= 0.0:
|
|
return {"ok": false, "reason": "screen scale unavailable"}
|
|
return {"ok": true, "reason": ""}
|
|
|
|
func screen_scale() -> float:
|
|
return DisplayServer.screen_get_scale(DisplayServer.window_get_current_screen())
|
|
|
|
func physical_size() -> Vector2i:
|
|
return DisplayServer.window_get_size()
|
|
|
|
func logical_size() -> Vector2i:
|
|
var scale := screen_scale()
|
|
var size := physical_size()
|
|
if scale <= 0.0:
|
|
return size
|
|
return Vector2i(roundi(size.x / scale), roundi(size.y / scale))
|
|
|
|
func viewport_size() -> Vector2i:
|
|
if _viewport == null or not is_instance_valid(_viewport):
|
|
return Vector2i.ZERO
|
|
return Vector2i(_viewport.get_visible_rect().size)
|
|
|
|
## Returns false (no request sent) when the size cannot fit the usable screen area.
|
|
func request_logical_size(size: Vector2i) -> bool:
|
|
var scale := screen_scale()
|
|
if scale <= 0.0 or size.x <= 0 or size.y <= 0:
|
|
return false
|
|
var target := Vector2i(roundi(size.x * scale), roundi(size.y * scale))
|
|
var screen := DisplayServer.window_get_current_screen()
|
|
var usable := DisplayServer.screen_get_usable_rect(screen)
|
|
var decorations := DisplayServer.window_get_size_with_decorations() - DisplayServer.window_get_size()
|
|
if target.x + decorations.x > usable.size.x or target.y + decorations.y > usable.size.y:
|
|
return false
|
|
DisplayServer.window_set_size(target)
|
|
# Keep the whole window on the usable area so the OS does not clamp the size back.
|
|
var position := DisplayServer.window_get_position()
|
|
var clamped := Vector2i(clampi(position.x, usable.position.x, usable.end.x - target.x - decorations.x),
|
|
clampi(position.y, usable.position.y, usable.end.y - target.y - decorations.y))
|
|
if clamped != position:
|
|
DisplayServer.window_set_position(clamped)
|
|
return true
|