godot-cli-control 0.2.17__tar.gz → 0.2.18__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/PKG-INFO +28 -9
  2. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/CHANGELOG.md +122 -21
  3. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/README.md +27 -8
  4. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/_version.py +2 -2
  5. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/.gitignore +0 -0
  6. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/LICENSE +0 -0
  7. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/LICENSE +0 -0
  8. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/README.md +0 -0
  9. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bin/run_cli_control.ps1 +0 -0
  10. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bin/run_cli_control.sh +0 -0
  11. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/capture_logger.gd +0 -0
  12. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/diagnostics_api.gd +0 -0
  13. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/error_codes.gd +0 -0
  14. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/game_bridge.gd +0 -0
  15. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/game_bridge.gd.uid +0 -0
  16. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/input_simulation_api.gd +0 -0
  17. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/input_simulation_api.gd.uid +0 -0
  18. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/low_level_api.gd +0 -0
  19. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/low_level_api.gd.uid +0 -0
  20. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/render_api.gd +0 -0
  21. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/scene_api.gd +0 -0
  22. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/time_api.gd +0 -0
  23. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/variant_codec.gd +0 -0
  24. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/bridge/wait_api.gd +0 -0
  25. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/plugin.cfg +0 -0
  26. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/plugin.gd +0 -0
  27. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/plugin.gd.uid +0 -0
  28. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_diagnostics_api.gd +0 -0
  29. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_game_bridge.gd +0 -0
  30. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_input_simulation_api.gd +0 -0
  31. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_low_level_api.gd +0 -0
  32. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_render_api.gd +0 -0
  33. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_scene_api.gd +0 -0
  34. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_time_api.gd +0 -0
  35. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_variant_codec.gd +0 -0
  36. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/gut/test_wait_api.gd +0 -0
  37. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/run_gut.py +0 -0
  38. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/run_gut.sh +0 -0
  39. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/test_init_walkthrough.py +0 -0
  40. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/addons/godot_cli_control/tests/test_walkthrough.sh +0 -0
  41. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/pyproject.toml +0 -0
  42. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/__init__.py +0 -0
  43. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/__main__.py +0 -0
  44. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/_duration.py +0 -0
  45. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/bridge.py +0 -0
  46. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/cli.py +0 -0
  47. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/client.py +0 -0
  48. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/daemon.py +0 -0
  49. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/init_cmd.py +0 -0
  50. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/pytest_plugin.py +0 -0
  51. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/registry.py +0 -0
  52. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/runner.py +0 -0
  53. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/skills_install.py +0 -0
  54. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/templates/__init__.py +0 -0
  55. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/templates/skill/SKILL.md +0 -0
  56. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/godot_cli_control/templates/skill/__init__.py +0 -0
  57. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/__init__.py +0 -0
  58. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_bridge.py +0 -0
  59. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_cli.py +0 -0
  60. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_cli_helpers.py +0 -0
  61. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_client.py +0 -0
  62. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_daemon.py +0 -0
  63. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_duration.py +0 -0
  64. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_client_direct.py +0 -0
  65. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_diag.py +0 -0
  66. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_example.py +0 -0
  67. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_input.py +0 -0
  68. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_record.py +0 -0
  69. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_render.py +0 -0
  70. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_scene.py +0 -0
  71. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_screenshot_gui.py +0 -0
  72. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_e2e_time.py +0 -0
  73. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_init.py +0 -0
  74. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_issue_fixes.py +0 -0
  75. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_pytest_plugin.py +0 -0
  76. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_registry.py +0 -0
  77. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_runner.py +0 -0
  78. {godot_cli_control-0.2.17 → godot_cli_control-0.2.18}/python/tests/test_skills_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: godot-cli-control
3
- Version: 0.2.17
3
+ Version: 0.2.18
4
4
  Summary: WebSocket bridge for headless / scripted control of Godot scenes.
5
5
  Project-URL: Homepage, https://github.com/ClaymanTwinkle/godot-cli-control
6
6
  Project-URL: Repository, https://github.com/ClaymanTwinkle/godot-cli-control
@@ -117,7 +117,10 @@ def test_jump(godot_daemon, bridge):
117
117
  ```
118
118
 
119
119
  - `godot_daemon` (session-scoped) starts headless Godot once and stops it after all tests; if a daemon is already running it's reused (and not stopped at teardown — keeps your IDE workflow alive).
120
- - `bridge` (function-scoped) gives a fresh `GameBridge`; on teardown it calls `release_all()` so a `hold` left behind by one case can't bleed into the next, then closes the connection.
120
+ - `bridge` (function-scoped) gives a fresh `GameBridge`; on teardown it best-effort restores global engine state — `unpause()`, `time_scale` back to its setup-time snapshot, `release_all()` so a `hold`/`pause`/speed-up left behind by one case can't bleed into the next, then closes the connection.
121
+ - `fresh_scene` (function-scoped, opt-in) calls `scene_reload()` at setup so the case starts on a pristine scene. Node references cached before the reload are invalid afterwards.
122
+ - `no_push_errors` (function-scoped, opt-in) records an `errors` marker at setup and fails the case if any new `push_error` was emitted during it (warnings don't fail). Requires Godot 4.5+ (Logger API) — older engines raise `RpcError` 1012 at setup, loudly.
123
+ - On a test failure (non-headless daemon only) a best-effort screenshot is saved to `.cli_control/failures/<nodeid>.png` and the path is attached to the pytest report.
121
124
 
122
125
  CLI options:
123
126
 
@@ -125,6 +128,7 @@ CLI options:
125
128
  --godot-cli-port=N # GameBridge port (default: read from .cli_control/port)
126
129
  --godot-cli-no-headless # open a real Godot window
127
130
  --godot-cli-project-root=DIR # default: pytest rootdir
131
+ --godot-cli-time-scale=X # Engine.time_scale applied at daemon startup (e.g. 5 to speed up the suite)
128
132
  ```
129
133
 
130
134
  ## CLI
@@ -139,17 +143,20 @@ godot-cli-control daemon start --record --movie-path X [--fps N] # 录制需
139
143
  godot-cli-control daemon stop [--all | --project PATH]
140
144
  godot-cli-control daemon status
141
145
  godot-cli-control daemon ls # list running daemons across all projects
146
+ godot-cli-control daemon logs [--tail N] # last N lines of .cli_control/godot.log (works post-mortem)
142
147
  godot-cli-control run <script.py> [--headless ...]
143
148
 
144
149
  # Read
145
150
  godot-cli-control tree [depth]
146
- godot-cli-control get <node_path> <prop>
151
+ godot-cli-control get <node_path> <prop> [prop2 ...] # multi-prop = atomic same-frame read
147
152
  godot-cli-control text <node_path>
148
153
  godot-cli-control exists <node_path> # exit 0=true, 1=false, 2=infra
149
154
  godot-cli-control visible <node_path> # exit 0=true, 1=false, 2=infra
150
155
  godot-cli-control children <node_path> [type-filter]
151
156
  godot-cli-control pressed
152
157
  godot-cli-control actions [--all]
158
+ godot-cli-control sprite-info <node_path> # Sprite2D/AnimatedSprite2D/TextureRect render state in one call
159
+ godot-cli-control errors [--since MARKER] [--limit N] # structured push_error/push_warning log (Godot 4.5+)
153
160
 
154
161
  # Write / call
155
162
  godot-cli-control set <node_path> <prop> <json-value>
@@ -164,21 +171,33 @@ godot-cli-control combo --steps-json '[...]' # or `combo file.json` / `combo -
164
171
  godot-cli-control combo-cancel
165
172
  godot-cli-control release-all
166
173
 
167
- # Wait
168
- godot-cli-control wait-node <node_path> [timeout] # exit 0=found, 1=timeout
169
- godot-cli-control wait-time <seconds>
174
+ # Wait (exit 0=hit, 1=timeout)
175
+ godot-cli-control wait-node <node_path> [timeout]
176
+ godot-cli-control wait-prop <node_path> <prop> <value> [--op gt|lt|ge|le|ne] [--timeout S] [--tolerance T]
177
+ godot-cli-control wait-signal <node_path> <signal> [timeout] # arm BEFORE triggering the action
178
+ godot-cli-control wait-frames <n> [--physics]
179
+ godot-cli-control wait-time <seconds> # game time — scales with time-scale
180
+
181
+ # Scene isolation
182
+ godot-cli-control scene-reload [--timeout S] # reload current scene, block until ready
183
+ godot-cli-control scene-change <res://path.tscn> # switch scene, block until ready
184
+
185
+ # Time control
186
+ godot-cli-control time-scale [value] # read (no arg) or set Engine.time_scale, (0, 100]
187
+ godot-cli-control pause | unpause
188
+ godot-cli-control step-frames <n> [--physics] # deterministic stepping while paused
170
189
 
171
190
  # Render (path is required as of 0.2.0)
172
- godot-cli-control screenshot <output.png>
191
+ godot-cli-control screenshot <output.png> [--node <node_path>] # --node crops to that node's screen rect
173
192
  ```
174
193
 
175
194
  ### Output contract
176
195
 
177
196
  - success: `{"ok": true, "result": <data>}` on stdout, exit 0
178
- - error: `{"ok": false, "error": {"code": N, "message": "..."}}` on stdout, exit 1 (RPC) or 2 (connection / usage)
197
+ - error: `{"ok": false, "error": {"code": N, "message": "..."}}` on stdout, exit 1 (RPC), 2 (connection / infra), or 64 (usage)
179
198
  - `--text` / `--no-json` switches back to the legacy human-readable strings; errors then go to stderr.
180
199
 
181
- `exists` / `visible` / `wait-node` propagate their boolean result to the exit code, so shell `if` works:
200
+ `exists` / `visible` and the `wait-*` commands propagate their boolean / hit-or-timeout result to the exit code, so shell `if` works:
182
201
 
183
202
  ```bash
184
203
  if godot-cli-control exists /root/Main/Boss; then
@@ -1,35 +1,146 @@
1
1
  # Changelog
2
2
 
3
+ 只记用户可感知的变更(feature / BREAKING / 行为修复);纯 CI、内部重构、文档微调不入账,明细见 git log。
4
+ 版本由 git tag 派生(hatch-vcs)。发版时 `release.sh` 会校验本文件:`[Unreleased]` 非空时拒绝打 tag,
5
+ 加 `--roll-changelog` 自动把 `[Unreleased]` 滚动为新版本段(issue #140)。
6
+
3
7
  ## [Unreleased]
4
8
 
9
+ ## [0.2.17] - 2026-06-06
10
+
11
+ ### Fixed
12
+ - **#137 `screenshot --node` 在 hiDPI / viewport stretch 下裁剪错位**(PR #138):`compute_node_screen_rect` 漏乘 viewport final transform——rect 算在画布(逻辑设计分辨率)坐标系,而取图侧 `get_image()` 是窗口物理像素系;平窗(scale=1)两系重合所以长期未暴露。Retina / `canvas_items` stretch 下裁出的区域偏移或截半。
13
+
14
+ ## [0.2.16] - 2026-06-06
15
+
5
16
  ### Added
17
+ - **`find_godot_binary` PATH 候选名补 .NET/mono 版别名**(PR #134):`godot4-mono` / `godot-mono` / `godot_mono` / `Godot_mono`,排标准名之后(双版并存优先轻量标准版)。macOS `/Applications` 与 Windows Program Files 的 glob 天然覆盖 mono 包名,零改动。
18
+ - **#135 macOS 检测兜底扫描 `~/Applications`**(PR #136):`/Applications` 优先、用户级目录兜底,覆盖无管理员权限安装的场景。
19
+
20
+ ## [0.2.15] - 2026-06-05
21
+
22
+ ### Added
23
+ - **feat(wait): `wait-prop` / `wait-signal` / `wait-frames` 条件等待原语 + 错误码 1007(#96)**: 三个新 CLI 子命令。`wait-prop <path> <prop> <value> [--op eq|ne|gt|lt|ge|le] [--timeout] [--tolerance]` 等属性满足条件(exit 0=matched, 1=timeout);`wait-signal <path> <signal> [--timeout]` 等信号发射(exit 0=emitted, 1=timeout);`wait-frames <N> [--physics]` 推进 N 帧。服务端错误码 1007 SIGNAL_NOT_FOUND(wait-signal 传未知信号名,永久性 schema 错,不应 retry)。Python `GameClient` 对应方法:`wait_property` / `wait_signal` / `wait_frames`。
6
24
  - **#110 `wait_signal` 超时与节点释放可区分**:未命中时返回体新增 `reason` 字段,值为 `"timeout"`(信号在超时前未发射)或 `"node_freed"`(等待期间目标节点被释放)。命中路径(`emitted: true`)不含 `reason`,对齐 `wait_property` matched 时无 reason 的约定。非 BREAKING(新增可选字段)。
25
+ - **feat: `get_properties` 多属性同帧原子读 + `get` 多属性形式(#100)**:`get <path> <prop1> <prop2>` 一次 RPC 同帧读取多个属性,不存在部分新鲜/部分旧数据的竞态;任一属性缺失整体失败(1002,message 点名所有缺失项)。sub-path 形式(`position:x`)在多属性列表中也支持。
26
+ - **feat(input): press/tap/hold/combo 注入 InputEventAction 进事件管线,_input/_unhandled_input 可见(#97)**:之前的实现只调 `Input.action_press/release`,仅翻轮询状态位,事件回调收不到。现改为 `Input.parse_input_event(InputEventAction)`,轮询(`is_action_pressed`/`get_vector`)与事件回调(`_input`/`_unhandled_input`)两种游戏写法均可感知注入输入。边界:`InputEventAction` 不携带鼠标坐标,依赖位置的 `_gui_input` 控件仍需用 `click`。
27
+ - **feat(scene): `scene-reload` / `scene-change` + pytest `fresh_scene` fixture + 错误码 1008(#98,PR #121)**:重载当前场景 / 切换到指定场景并阻塞到新场景 ready(per-test 隔离原语);无 current scene、路径不存在、超时报 1008。`fresh_scene` fixture 让用例从干净场景开始;reload 后此前缓存的节点路径全部失效。
28
+ - **feat(time): `time-scale` / `pause` / `unpause` / `step-frames` + 错误码 1009(#102,PR #125)**:读写 `Engine.time_scale`(合法域 (0, 100],`wait-time` 按 game time 计、倍速后语义不变)、暂停/恢复 SceneTree、paused 下确定性推进 N 帧(物理断言原语)。未 pause 调 `step-frames` 报 1009。
29
+ - **feat(render): `sprite-info` + `screenshot --node` + 错误码 1010/1011(#101,PR #130)**:`sprite-info` 一次拿齐 Sprite2D / AnimatedSprite2D / TextureRect 的 texture、实际图集区域(effective_region / frame_texture)、翻转、帧号、modulate、visible(纯属性读,headless 可用);`screenshot --node` 按节点屏幕 AABB 裁剪小图供像素级断言。非 sprite 类 → 1010,屏幕外/零尺寸 → 1011。
30
+ - **feat(diag): `errors` 结构化查询 + `daemon logs --tail` + pytest 失败配套 + 错误码 1012(#103,PR #131)**:Logger 拦截 push_error / push_warning,seq 游标增量查询(`--since` 实现「本用例期间零错误」断言;需 Godot 4.5+,老引擎报 1012);`daemon logs --tail N` 直接读 `.cli_control/godot.log`(daemon 已退出也可用,post-mortem 排查);pytest 新增 `no_push_errors` fixture(用例期间出现新 push_error 即失败)与失败自动截图(非 headless 时存 `.cli_control/failures/<nodeid>.png`)。
31
+ - **feat(init): 默认覆盖 addon 目录 + `--keep-addon` 逃生口(PR #133)**:重跑 `init` 即同步 `addons/godot_cli_control/` 与 SKILL.md 到当前 CLI 版本(插件目录 wipe 后重拷,`project.godot` patch 保持幂等)。`--force` 降级为兼容 no-op(与 `--keep-addon` 互斥)。
7
32
 
8
- ### Fixed (BREAKING — exit code changes)
33
+ ### Changed
34
+ - **BREAKING** `get` 复合 Variant 返回从旧版 `"(x, y)"` 字符串改为 set-schema 数组 + type 字段;信封 result 从裸值变 `{"value": <array-or-scalar>, "type"?: "<GodotType>"}` 对象(#99)。写侧 `set` 接受的 Array layout 完全一致,get→set round-trip 无需转换。Python API `get_property` / `get_properties` 只返回裸 value(type 已剥离),要拿 type 字段走 CLI `get` 或底层 `client.request("get_property", ...)`。
35
+ - **pytest `bridge` fixture teardown 兜底还原全局态(#124,PR #126)**:unpause + 把 `time_scale` 还原到 setup 时快照值(非盲写 1.0,不打断 `--godot-cli-time-scale` 整套加速)+ `release_all()`。上个用例崩溃残留的 pause / 倍速不再污染下一个。
9
36
 
37
+ ### Fixed (BREAKING — exit code changes)
10
38
  - **#111 argparse 用法错 exit 2 → exit 64**: 所有 argparse 层错误(缺位置参数、非法 choices、未知子命令等)现在统一输出 `-1003` 信封并以 exit 64 退出。之前是 exit 2(argparse 默认)。影响:脚本若用 `[ $? -eq 2 ]` 判 argparse 错需改为 `[ $? -eq 64 ]`。
11
39
  - **#92 `run <script>` 前置错误拆分**:
12
40
  - 脚本路径不存在 / 脚本缺 `run(bridge)` 函数:由旧的 exit 2(或 exit 1)改为 exit 64,错误码由 `-1003` 保持不变。这是用法错,应修正调用而非重试。
13
41
  - `daemon start`(`run` 自动启停 / `daemon start` 命令)/ `daemon stop` 系统级失败(端口冲突、找不到 Godot binary、PID 文件丢失等):错误码由 `-1003` 改为 **新码 `-1006` (PRECONDITION)**,exit code 保持 exit 2。影响:`run`/`daemon` 失败时,通过 `error.code` 区分用法错(`-1003` → 修调用)与基础设施错(`-1006` → 修环境)现在变得无歧义。
14
42
 
43
+ ### Performance
44
+ - **#109 `wait_property` 免物化直比 + 属性存在性 memo(PR #132)**:逐帧轮询不再每帧物化编码整个属性值;顺手修了 GDScript 跨类型 `==` 是运行期脚本错误(中止函数抛 Nil、非静默 false)的存量雷(`_safe_eq` 守卫)。
45
+
46
+ ## [0.2.14] - 2026-06-03
47
+
48
+ 仅打包元数据 / 文档:PyPI keywords / classifiers / urls、社区健康文件(CONTRIBUTING / SECURITY / issue & PR 模板)、修复失效的 git 安装命令。无代码行为变更。
49
+
50
+ ## [0.2.13] - 2026-06-02
51
+
15
52
  ### Added
16
- - **feat(wait): `wait-prop` / `wait-signal` / `wait-frames` 条件等待原语 + 错误码 1007(#96)**: 三个新 CLI 子命令。`wait-prop <path> <prop> <value> [--op eq|ne|gt|lt|ge|le] [--timeout] [--tolerance]` 等属性满足条件(exit 0=matched, 1=timeout);`wait-signal <path> <signal> [--timeout]` 等信号发射(exit 0=emitted, 1=timeout);`wait-frames <N> [--physics]` 推进 N 帧。服务端错误码 1007 SIGNAL_NOT_FOUND(wait-signal 传未知信号名,永久性 schema 错,不应 retry)。Python `GameClient` 对应方法:`wait_property` / `wait_signal` / `wait_frames`。
53
+ - **examples/platformer-demo 可跑示例(#89)**:自带 Start 按钮 + 跳跃角色场景与 `drive.sh` 全链路演示,CI 下真 Godot 驱动防腐。
17
54
 
18
- ### Changed
19
- - **BREAKING** `get` 复合 Variant 返回从旧版 `"(x, y)"` 字符串改为 set-schema 数组 + type 字段;信封 result 从裸值变 `{"value": <array-or-scalar>, "type"?: "<GodotType>"}` 对象(#99)。写侧 `set` 接受的 Array layout 完全一致,get→set round-trip 无需转换。Python API `get_property` / `get_properties` 只返回裸 value(type 已剥离),要拿 type 字段走 CLI `get` 或底层 `client.request("get_property", ...)`。
55
+ ### Fixed
56
+ - **批量修复 10 issue**:CLI 子命令端口从 `.cli_control/port` 自动发现、`-1003` 用法错退出码统一为 64(#82)、`tap`/`hold` `--wait` 阻塞选项、ruff CI 门禁等。
57
+
58
+ ## [0.2.12] - 2026-06-01
20
59
 
21
60
  ### Added
22
- - **feat: `get_properties` 多属性同帧原子读 + `get` 多属性形式(#100)**:`get <path> <prop1> <prop2>` 一次 RPC 同帧读取多个属性,不存在部分新鲜/部分旧数据的竞态;任一属性缺失整体失败(1002,message 点名所有缺失项)。sub-path 形式(`position:x`)在多属性列表中也支持。
61
+ - **#80 `init` `.cli_control/` 写进目标项目 `.gitignore`**,避免提交机器本地状态(port / pid / log)。
62
+
63
+ ## [0.2.11] - 2026-05-31
64
+
65
+ ### Fixed
66
+ - **#79 拒绝 `--record` + `--headless` 组合**:Godot Movie Maker 在 headless 下 SIGSEGV(上游问题),preflight 直接报用法错(下游 CultivationWorld #180 根因)。
67
+
68
+ ## [0.2.10] - 2026-05-24
69
+
70
+ ### Fixed
71
+ - Windows / 测试修补批次:`run_gut.py` 强制 UTF-8 stdout/stderr(#36 Windows GUT 中文崩溃)、Windows 注册表目录(#43)、`run` 收尾 SIGTERM 噪音消除(#67)、`_build_tree` LIMIT 常量消歧(#48)。
72
+
73
+ ## [0.2.9] - 2026-05-23
74
+
75
+ ### Fixed
76
+ - **输入持续性**:断连按 WebSocket close code 区分「清 / 不清」持有中的输入;`hold` duration 加校验。崩断的客户端不再把按住的方向键永久遗留在游戏里。
77
+
78
+ ## [0.2.8] - 2026-05-16
79
+
80
+ ### Added
81
+ - **`GODOT_CLI_LONG_OP_TIMEOUT=<seconds>`** 环境变量抬高 600s 客户端长操作上限(超 10 分钟的录制);`wait_game_time` / `combo` 去掉 wall-clock 上限,靠 ws 心跳兜底(#45)。
82
+ - **`run` 静态检测脚本里的 `screenshot` 自动 force GUI(#65)**:headless 截图必失败,提前规避。
83
+
84
+ ## [0.2.7] - 2026-05-16
85
+
86
+ 仅文档:SKILL.md / `-h` / 旁路文档以代码为标准对齐(11 处缺漏与不准确)。无代码行为变更。
87
+
88
+ ## [0.2.6] - 2026-05-16
89
+
90
+ ### Fixed
91
+ - **#61 `screenshot` 首帧 transient 失败根因消除 + 兜底**:viewport texture 未就绪时不再偶发 1006。
92
+
93
+ ## [0.2.5] - 2026-05-16
23
94
 
24
95
  ### Fixed
25
96
  - **#52 `set` 走 JSON Array 喂 Vector/Color/Rect 时静默失败**:`set zoom '[1.8, 1.8]'` 等价于 `node.set("zoom", [1.8, 1.8])`,Godot 隐式构造失败 → 实际值是 `Vector2(0,0)` 或被 clamp 到 `0.00001`,但服务端仍返 `{success: true}`。`handle_set_property` 现在查 `get_property_list()` 拿声明类型,把 numeric Array 转成对应 Variant。长度不匹配或元素非数字时 fail-loud 返 `-32602 "value type mismatch ..."`,不再 silent corruption。
26
97
  - **sub-path 标量赋值现在真的会写入**:之前 `set <node> position:x 1.8` 调的是 `Object.set("position:x", 1.8)`,Godot 4 的 `Object.set` 把整串当字面属性名找不到就 silent no-op(依旧返 `{success: true}` 但 `position.x` 不变)。改用 `Object.set_indexed(NodePath, value)` 才会按 sub-path 写入。是 #54 review 阶段被新加的 `test_set_subpath_scalar_still_works` 捕获的隐藏 silent-fail。
98
+ - **CLI flag position**: `--json` / `--text` / `--no-json` 现在在 RPC 子命令尾部也接受(之前只能写最前面)。argparse 子 parser 用 `default=argparse.SUPPRESS` + 顶层 `set_defaults` 兜底,避免子 parser 默认值覆盖父 parser 解析结果。
99
+ - **Error code 1004 collision**: `low_level_api.gd` 的 scene tree 超限改用新业务码 `1005 "scene tree too large"`,与 input_simulation `1004 "combo in progress"` 解耦。新增 `error_codes.gd` 集中常量。
100
+ - **pytest fixture default port**: `--godot-cli-port` 默认从 9877 改为 0(OS-assigned),与 `daemon start` 默认对齐;多项目并行测试不再撞端口。
101
+ - **Addon README error-code table**: 之前只列到 1003,补全 1004 / 1005 / 客户端 -1xxx 段。
102
+ - **Scene tree hard limit bypass (DoS fix)**: `handle_get_scene_tree` 入口现在把 `max_nodes` clamp 到硬墙 `_BUILD_TREE_MAX_NODES` (5000)。修复前客户端传 `max_nodes=999999` 会让服务端先把整棵超大树构造成 Dictionary 再被 1005 错误丢弃(内存浪费 / OOM 路径)。
103
+ - **Error code 1003 semantic split**: screenshot 在 viewport texture 为 null 时不再借用 `1003 METHOD_NOT_FOUND`,改用新业务码 `1006 RESOURCE_UNAVAILABLE`。1003 现在是纯 schema 错(不应 retry),1006 是 transient 错(短重试可能成功),agent 据此分别处置。
104
+ - **`cmd_run` / `cmd_init` 真正输出 JSON envelope(#50)**;`GameBridge` 补 `get_scene_tree` / `wait_game_time` / `action_tap` alias 兑现「方法名一致」承诺(#58 #60)。
27
105
 
28
106
  ### Added
29
- - **feat(input): press/tap/hold/combo 注入 InputEventAction 进事件管线,_input/_unhandled_input 可见(#97)**:之前的实现只调 `Input.action_press/release`,仅翻轮询状态位,事件回调收不到。现改为 `Input.parse_input_event(InputEventAction)`,轮询(`is_action_pressed`/`get_vector`)与事件回调(`_input`/`_unhandled_input`)两种游戏写法均可感知注入输入。边界:`InputEventAction` 不携带鼠标坐标,依赖位置的 `_gui_input` 控件仍需用 `click`。
30
107
  - **#54 全部复合 Variant 都支持 Array → Variant coerce**:从 4-float 简单类型到 16-float 矩阵,全部按 axis-vector 顺序的 flat numeric Array 写入(每 N 元素 = 一个 Vector 轴)。覆盖:`Vector2/2i/3/3i/4/4i`、`Rect2/2i`、`Color`(3-element=RGB / 4-element=RGBA)、`Plane(a,b,c,d)`、`Quaternion(x,y,z,w)`、`AABB(pos 3, size 3)`、`Basis(9 axis-vector)`、`Transform2D(xaxis 2, yaxis 2, origin 2)`、`Transform3D(basis 9, origin 3)`、`Projection(16 axis-vector)`。具体 layout 见 SKILL.md。`Plane.normal` 和 `Quaternion` 不会自动归一化(与 Godot ctor 一致)。
31
108
  - **#54 防御性 fallback**:未在 coerce 名单也不在 `_ARRAY_PASSTHROUGH_SAFE_TYPES`(基本类型 / Object / 集合 / Packed*Array)白名单的声明类型 + Array 输入会 fail-loud,避免未来 Godot 加新 compound Variant 时 silent-corrupt 回归。
32
109
  - **#54 sub-path + Array 现在 fail-loud**:`set <node> transform:origin '[10, 20, 30]'` 在 Godot 里会 silent-corrupt(Vector3 leaf 不会从 Array 隐式构造,origin 仍是 (0,0,0)),server 现在主动返 `-32602 "sub-path + Array is not supported"` 提示走 top-level 形式(如 `set <node> transform '[basis 9, origin 3]'`)。sub-path 标量赋值(`position:x 1.8`)不变。
110
+ - `tree --max-nodes <N>`(默认 200):节点数软上限;超出时响应含 `truncated: true` + `total_nodes`,agent 据此决定分子树。硬墙仍是 5000 节点 → `1005`。
111
+ - `set` / `call --text-value`:禁用 JSON 解析、把 value/args 强制按字符串处理,避开 `null` / `true` / `42` 这类字面量被解析成 Variant 类型的 footgun。
112
+
113
+ ### Changed
114
+ - **BREAKING (轻微)**:`daemon start` / `run` 默认 headless 行为改为基于 `sys.stdout.isatty()` 自动判定 —— pipe / CI / agent shell 默认 headless;交互终端默认开窗。新增 `--gui` 强制开窗 flag。`--headless` 仍可显式传,覆盖自动判。脚本里依赖 "默认会开窗" 的需要加 `--gui`。
115
+
116
+ ## [0.2.4] - 2026-05-01
117
+
118
+ ### Changed
119
+ - **BREAKING (轻微):默认 daemon 端口从 9877 改为 OS-assigned**。实际端口写入 `.cli_control/port`,CLI 子命令与 `GameClient()`(不传 port)自动发现。硬编码 `127.0.0.1:9877` 的外部脚本要么读 `.cli_control/port`,要么显式传 `--port 9877`。
120
+
121
+ ### Added
122
+ - **`daemon ls`**:跨项目列出运行中的 daemon(全局注册表 + 死记录自动清理)。
123
+ - **`daemon stop --all` / `--project <path>`**:批量 / 跨项目停止。
124
+ - **`daemon start --idle-timeout 30m`**:opt-in 空闲自动 shutdown;项目级默认可写 `.cli_control/config.json`(`{"idle_timeout": "30m"}`),显式 flag 优先。
125
+ - `daemon start` 前先 bind 校验端口空闲,被占立即报错。
126
+
127
+ ## [0.2.3] - 2026-05-01
128
+
129
+ ### Added
130
+ - **`GODOT_CLI_CONTROL=0` 强制禁用逃生口**:env var 一票否决,覆盖其它激活方式。
131
+
132
+ ## [0.2.2] - 2026-04-29
133
+
134
+ ### Security / Hardening
135
+ - **封堵 `set_property` 黑名单 NodePath sub-property 旁路**(`script:source_code` 类路径绕过黑名单)。
136
+ - handler 加固:async type-check、action 名校验、scene-tree 节点数上限。
137
+ - Python 测试覆盖率接入 80% 门禁。
138
+
139
+ ## [0.2.1] - 2026-04-29
140
+
141
+ 与 v0.2.0 同代码(发布管线重跑),无变更。
142
+
143
+ ## [0.2.0] - 2026-04-29
33
144
 
34
145
  ### AI-friendly CLI 改造(多个 BREAKING change)
35
146
 
@@ -73,24 +184,14 @@
73
184
  #### Out of scope (intentional)
74
185
  - `run <script.py>` 子命令保留旧的人类可读 stderr 输出。它是交互式脚本宿主,不是 RPC,新的 JSON 信封契约只覆盖 RPC 子命令 + daemon 三命令。
75
186
 
76
- ### AI-friendliness review fixes (2026-05-11)
77
-
78
- #### Fixed
79
- - **CLI flag position**: `--json` / `--text` / `--no-json` 现在在 RPC 子命令尾部也接受(之前只能写最前面)。argparse 子 parser 用 `default=argparse.SUPPRESS` + 顶层 `set_defaults` 兜底,避免子 parser 默认值覆盖父 parser 解析结果。
80
- - **Error code 1004 collision**: `low_level_api.gd` 的 scene tree 超限改用新业务码 `1005 "scene tree too large"`,与 input_simulation `1004 "combo in progress"` 解耦。新增 `error_codes.gd` 集中常量。
81
- - **pytest fixture default port**: `--godot-cli-port` 默认从 9877 改为 0(OS-assigned),与 `daemon start` 默认对齐;多项目并行测试不再撞端口。
82
- - **Addon README error-code table**: 之前只列到 1003,补全 1004 / 1005 / 客户端 -1xxx 段。
83
- - **Scene tree hard limit bypass (DoS fix)**: `handle_get_scene_tree` 入口现在把 `max_nodes` clamp 到硬墙 `_BUILD_TREE_MAX_NODES` (5000)。修复前客户端传 `max_nodes=999999` 会让服务端先把整棵超大树构造成 Dictionary 再被 1005 错误丢弃(内存浪费 / OOM 路径)。
84
- - **Error code 1003 semantic split**: screenshot 在 viewport texture 为 null 时不再借用 `1003 METHOD_NOT_FOUND`,改用新业务码 `1006 RESOURCE_UNAVAILABLE`。1003 现在是纯 schema 错(不应 retry),1006 是 transient 错(短重试可能成功),agent 据此分别处置。
187
+ ## [0.1.7] [0.1.10] - 2026-04-29
85
188
 
86
- #### Added
87
- - `tree --max-nodes <N>`(默认 200):节点数软上限;超出时响应含 `truncated: true` + `total_nodes`,agent 据此决定分子树。硬墙仍是 5000 节点 → `1005`。
88
- - `set` / `call --text-value`:禁用 JSON 解析、把 value/args 强制按字符串处理,避开 `null` / `true` / `42` 这类字面量被解析成 Variant 类型的 footgun。
189
+ 发布管线 / 打包修复批次:`pyproject.toml` 提升到仓库根(0.1.7)、`init` 重新导入项目以刷新 `global_script_class_cache`(0.1.9)。
89
190
 
90
- #### Changed
91
- - **BREAKING (轻微)**:`daemon start` / `run` 默认 headless 行为改为基于 `sys.stdout.isatty()` 自动判定 —— pipe / CI / agent shell 默认 headless;交互终端默认开窗。新增 `--gui` 强制开窗 flag。`--headless` 仍可显式传,覆盖自动判。脚本里依赖 "默认会开窗" 的需要加 `--gui`。
191
+ ### Changed
192
+ - **`auto_enable_in_debug` 默认改为 true**(0.1.10):编辑器 F5 直接生效,不再需要手动激活(release build 仍无条件禁用)。
92
193
 
93
- ## [0.1.6] - Unreleased
194
+ ## [0.1.6] - 2026-04-29
94
195
 
95
196
  ### Added
96
197
  - `godot-cli-control init`: 一键 onboarding 子命令 + 跨平台 Python daemon (取代手写 wrapper)。
@@ -80,7 +80,10 @@ def test_jump(godot_daemon, bridge):
80
80
  ```
81
81
 
82
82
  - `godot_daemon` (session-scoped) starts headless Godot once and stops it after all tests; if a daemon is already running it's reused (and not stopped at teardown — keeps your IDE workflow alive).
83
- - `bridge` (function-scoped) gives a fresh `GameBridge`; on teardown it calls `release_all()` so a `hold` left behind by one case can't bleed into the next, then closes the connection.
83
+ - `bridge` (function-scoped) gives a fresh `GameBridge`; on teardown it best-effort restores global engine state — `unpause()`, `time_scale` back to its setup-time snapshot, `release_all()` so a `hold`/`pause`/speed-up left behind by one case can't bleed into the next, then closes the connection.
84
+ - `fresh_scene` (function-scoped, opt-in) calls `scene_reload()` at setup so the case starts on a pristine scene. Node references cached before the reload are invalid afterwards.
85
+ - `no_push_errors` (function-scoped, opt-in) records an `errors` marker at setup and fails the case if any new `push_error` was emitted during it (warnings don't fail). Requires Godot 4.5+ (Logger API) — older engines raise `RpcError` 1012 at setup, loudly.
86
+ - On a test failure (non-headless daemon only) a best-effort screenshot is saved to `.cli_control/failures/<nodeid>.png` and the path is attached to the pytest report.
84
87
 
85
88
  CLI options:
86
89
 
@@ -88,6 +91,7 @@ CLI options:
88
91
  --godot-cli-port=N # GameBridge port (default: read from .cli_control/port)
89
92
  --godot-cli-no-headless # open a real Godot window
90
93
  --godot-cli-project-root=DIR # default: pytest rootdir
94
+ --godot-cli-time-scale=X # Engine.time_scale applied at daemon startup (e.g. 5 to speed up the suite)
91
95
  ```
92
96
 
93
97
  ## CLI
@@ -102,17 +106,20 @@ godot-cli-control daemon start --record --movie-path X [--fps N] # 录制需
102
106
  godot-cli-control daemon stop [--all | --project PATH]
103
107
  godot-cli-control daemon status
104
108
  godot-cli-control daemon ls # list running daemons across all projects
109
+ godot-cli-control daemon logs [--tail N] # last N lines of .cli_control/godot.log (works post-mortem)
105
110
  godot-cli-control run <script.py> [--headless ...]
106
111
 
107
112
  # Read
108
113
  godot-cli-control tree [depth]
109
- godot-cli-control get <node_path> <prop>
114
+ godot-cli-control get <node_path> <prop> [prop2 ...] # multi-prop = atomic same-frame read
110
115
  godot-cli-control text <node_path>
111
116
  godot-cli-control exists <node_path> # exit 0=true, 1=false, 2=infra
112
117
  godot-cli-control visible <node_path> # exit 0=true, 1=false, 2=infra
113
118
  godot-cli-control children <node_path> [type-filter]
114
119
  godot-cli-control pressed
115
120
  godot-cli-control actions [--all]
121
+ godot-cli-control sprite-info <node_path> # Sprite2D/AnimatedSprite2D/TextureRect render state in one call
122
+ godot-cli-control errors [--since MARKER] [--limit N] # structured push_error/push_warning log (Godot 4.5+)
116
123
 
117
124
  # Write / call
118
125
  godot-cli-control set <node_path> <prop> <json-value>
@@ -127,21 +134,33 @@ godot-cli-control combo --steps-json '[...]' # or `combo file.json` / `combo -
127
134
  godot-cli-control combo-cancel
128
135
  godot-cli-control release-all
129
136
 
130
- # Wait
131
- godot-cli-control wait-node <node_path> [timeout] # exit 0=found, 1=timeout
132
- godot-cli-control wait-time <seconds>
137
+ # Wait (exit 0=hit, 1=timeout)
138
+ godot-cli-control wait-node <node_path> [timeout]
139
+ godot-cli-control wait-prop <node_path> <prop> <value> [--op gt|lt|ge|le|ne] [--timeout S] [--tolerance T]
140
+ godot-cli-control wait-signal <node_path> <signal> [timeout] # arm BEFORE triggering the action
141
+ godot-cli-control wait-frames <n> [--physics]
142
+ godot-cli-control wait-time <seconds> # game time — scales with time-scale
143
+
144
+ # Scene isolation
145
+ godot-cli-control scene-reload [--timeout S] # reload current scene, block until ready
146
+ godot-cli-control scene-change <res://path.tscn> # switch scene, block until ready
147
+
148
+ # Time control
149
+ godot-cli-control time-scale [value] # read (no arg) or set Engine.time_scale, (0, 100]
150
+ godot-cli-control pause | unpause
151
+ godot-cli-control step-frames <n> [--physics] # deterministic stepping while paused
133
152
 
134
153
  # Render (path is required as of 0.2.0)
135
- godot-cli-control screenshot <output.png>
154
+ godot-cli-control screenshot <output.png> [--node <node_path>] # --node crops to that node's screen rect
136
155
  ```
137
156
 
138
157
  ### Output contract
139
158
 
140
159
  - success: `{"ok": true, "result": <data>}` on stdout, exit 0
141
- - error: `{"ok": false, "error": {"code": N, "message": "..."}}` on stdout, exit 1 (RPC) or 2 (connection / usage)
160
+ - error: `{"ok": false, "error": {"code": N, "message": "..."}}` on stdout, exit 1 (RPC), 2 (connection / infra), or 64 (usage)
142
161
  - `--text` / `--no-json` switches back to the legacy human-readable strings; errors then go to stderr.
143
162
 
144
- `exists` / `visible` / `wait-node` propagate their boolean result to the exit code, so shell `if` works:
163
+ `exists` / `visible` and the `wait-*` commands propagate their boolean / hit-or-timeout result to the exit code, so shell `if` works:
145
164
 
146
165
  ```bash
147
166
  if godot-cli-control exists /root/Main/Boss; then
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.2.17'
22
- __version_tuple__ = version_tuple = (0, 2, 17)
21
+ __version__ = version = '0.2.18'
22
+ __version_tuple__ = version_tuple = (0, 2, 18)
23
23
 
24
24
  __commit_id__ = commit_id = None