@satelliteoflove/godot-mcp 3.23.0 → 3.23.1

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.
@@ -13,8 +13,10 @@ var _active: bool = false
13
13
  var _specs: Array = [] # [{node, fields: [{key, resolver}]}]
14
14
  var _hz: int = 20
15
15
  var _duration_ms: int = 1000
16
- var _start_time: int = 0
17
- var _stop_time: int = 0 # set on auto-stop or manual stop; 0 = still running
16
+ # GAME time accumulated (unpaused, time_scale-scaled), NOT wall clock: under a
17
+ # godot_game_time freeze the tree is paused between steps, and counting wall time
18
+ # there would run the window down / log stale samples before the step that moves.
19
+ var _elapsed_ms: float = 0.0
18
20
  var _frame_index: int = 0
19
21
  var _sample_interval: int = 1 # sample every N frames
20
22
  var _samples: Dictionary = {} # field_key -> Array of {t_ms, value}
@@ -41,7 +43,7 @@ func start(specs: Array, hz: int, duration_ms: int, signal_specs: Array = []) ->
41
43
  _field_truncated = {}
42
44
  _hz = clampi(hz, 1, 60)
43
45
  _duration_ms = clampi(duration_ms, 100, 5000)
44
- _start_time = Time.get_ticks_msec()
46
+ _elapsed_ms = 0.0
45
47
  _frame_index = 0
46
48
  _sample_interval = max(1, int(Engine.get_frames_per_second() / _hz)) if Engine.get_frames_per_second() > 0 else max(1, int(60.0 / _hz))
47
49
 
@@ -70,7 +72,6 @@ func start(specs: Array, hz: int, duration_ms: int, signal_specs: Array = []) ->
70
72
  if not resolved_fields.is_empty():
71
73
  _specs.append({"node": node, "node_path": node_path, "fields": resolved_fields})
72
74
 
73
- _stop_time = 0
74
75
  _active = true
75
76
  set_process(true)
76
77
 
@@ -174,7 +175,7 @@ func _record_event(source: String, sig_name: String, args: Array) -> void:
174
175
  _events_truncated = true
175
176
  return
176
177
  var ev := {
177
- "t_ms": Time.get_ticks_msec() - _start_time,
178
+ "t_ms": int(_elapsed_ms),
178
179
  "source": source,
179
180
  "signal": sig_name,
180
181
  }
@@ -217,18 +218,26 @@ func _exit_tree() -> void:
217
218
  _disconnect_all()
218
219
 
219
220
 
220
- func _process(_delta: float) -> void:
221
+ func _process(delta: float) -> void:
221
222
  if not _active:
222
223
  return
223
224
 
224
- var elapsed := Time.get_ticks_msec() - _start_time
225
+ # Measure the window in GAME time and only sample while it actually advances.
226
+ # The sampler inherits PROCESS_MODE_ALWAYS, so _process keeps firing under a
227
+ # godot_game_time freeze (tree paused) even though gameplay is static. Counting
228
+ # those frames would (1) fill the window with stale values and (2) run the
229
+ # auto-stop down during frozen idle, so a later step lands outside the window.
230
+ # Skipping while paused makes the window span only live gameplay / step time.
231
+ var tree := get_tree()
232
+ if tree != null and tree.paused:
233
+ return
234
+ _elapsed_ms += delta * 1000.0 # time_scale-scaled, unpaused-only == game time
225
235
 
226
- if elapsed >= _duration_ms:
236
+ if _elapsed_ms >= float(_duration_ms):
227
237
  _active = false
228
- _stop_time = Time.get_ticks_msec()
229
238
  set_process(false)
230
239
  # Window over: stop recording signal emissions too. An emission landing
231
- # between duration_ms elapsing and this frame is recorded with t_ms
240
+ # between the window closing and this frame is recorded with t_ms
232
241
  # slightly past the window -- harmless and honest.
233
242
  _disconnect_all()
234
243
  return
@@ -246,7 +255,7 @@ func _process(_delta: float) -> void:
246
255
  for field_info in spec.fields:
247
256
  var arr: Array = _samples.get(field_info.full_key, [])
248
257
  if arr.size() < MAX_SAMPLES_PER_FIELD:
249
- arr.append({"t_ms": elapsed, "value": "freed"})
258
+ arr.append({"t_ms": int(_elapsed_ms), "value": "freed"})
250
259
  else:
251
260
  _field_truncated[field_info.full_key] = true
252
261
  continue
@@ -257,19 +266,13 @@ func _process(_delta: float) -> void:
257
266
  continue
258
267
  var arr: Array = _samples.get(field_info.full_key, [])
259
268
  if arr.size() < MAX_SAMPLES_PER_FIELD:
260
- arr.append({"t_ms": elapsed, "value": value})
269
+ arr.append({"t_ms": int(_elapsed_ms), "value": value})
261
270
  else:
262
271
  _field_truncated[field_info.full_key] = true
263
272
 
264
273
 
265
274
  func collect() -> Dictionary:
266
- var elapsed: int
267
- if _stop_time > 0:
268
- elapsed = _stop_time - _start_time
269
- elif _start_time > 0:
270
- elapsed = Time.get_ticks_msec() - _start_time
271
- else:
272
- elapsed = 0 # never started
275
+ var elapsed := int(_elapsed_ms)
273
276
  var total_samples := 0
274
277
  for key in _samples:
275
278
  total_samples += (_samples[key] as Array).size()
@@ -288,11 +291,9 @@ func collect() -> Dictionary:
288
291
 
289
292
 
290
293
  func stop() -> Dictionary:
294
+ # _elapsed_ms already holds the game-time window and freezes once _active is
295
+ # false, so a late manual stop can't inflate window_ms past the real window end.
291
296
  _active = false
292
- # Don't clobber an auto-stop's timestamp: a late manual stop would inflate
293
- # window_ms to the call time instead of the actual window end.
294
- if _stop_time == 0:
295
- _stop_time = Time.get_ticks_msec()
296
297
  set_process(false)
297
298
  _disconnect_all()
298
299
  return collect()
package/addon/plugin.cfg CHANGED
@@ -3,6 +3,6 @@
3
3
  name="Godot MCP"
4
4
  description="Model Context Protocol server for AI assistant integration"
5
5
  author="godot-mcp"
6
- version="3.23.0"
6
+ version="3.23.1"
7
7
  script="plugin.gd"
8
8
  godot_version_min="4.5"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@satelliteoflove/godot-mcp",
3
- "version": "3.23.0",
3
+ "version": "3.23.1",
4
4
  "description": "MCP server that gives AI assistants eyes and hands in the Godot editor: scene editing, input injection, deterministic game-time control, and live runtime state for agent-driven playtesting",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",