fix(world-map): 🐛 Fix race condition in SubViewport layout by explicitly setting size to window dimensions to stabilize scaling and zoom behavior

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Claude Code 2026-04-11 06:43:23 -07:00
parent 28e9994e39
commit 6cee670e77

View file

@ -129,20 +129,32 @@ func _apply_arena_camera_fit() -> void:
bg_vp = vwm.call("get_background_viewport") as SubViewport
if bg_vp != null:
bg_vp.render_target_update_mode = SubViewport.UPDATE_ALWAYS
# Force the SubViewport to match the actual window size. The default
# layout-driven _sync_background_size in viewport_window_manager.gd
# can leave the SubViewport at its initial ~240x180 if the Control's
# layout hasn't propagated. With stretch=true on the container, that
# tiny SubViewport gets blown up 5x to fill the window — making the
# hex map look like it's at zoom 0.45 even though the Camera2D.zoom
# is 0.126. Setting the size explicitly here side-steps the layout
# race condition.
var win_size: Vector2i = DisplayServer.window_get_size()
bg_vp.size = win_size
var viewport: Viewport = _world_map.get_viewport()
if viewport == null:
return
var view_size: Vector2 = viewport.get_visible_rect().size
cam.fit_map_to_viewport(int(game_map.width), int(game_map.height), view_size)
# Use the WINDOW size for the fit calculation, not the root viewport's
# get_visible_rect (which is fixed at the project's base 1920x1080).
var win_size_f: Vector2 = Vector2(DisplayServer.window_get_size())
cam.fit_map_to_viewport(int(game_map.width), int(game_map.height), win_size_f)
print(
(
"[AI ARENA] camera fit: map=%dx%d viewport=%dx%d zoom=%.3f"
"[AI ARENA] camera fit: map=%dx%d window=%dx%d zoom=%.3f"
% [
int(game_map.width),
int(game_map.height),
int(view_size.x),
int(view_size.y),
int(win_size_f.x),
int(win_size_f.y),
cam.zoom.x,
]
)
@ -252,6 +264,14 @@ func _capture_viewport_screenshot_async() -> void:
await tree.process_frame
await tree.process_frame
# Re-apply the camera fit right before capture so we know the camera
# is at the fit zoom regardless of what else may have touched it.
_apply_arena_camera_fit()
for _i: int in range(2):
await tree.process_frame
RenderingServer.force_draw()
await tree.process_frame
var viewport: Viewport = _world_map.get_viewport()
if viewport == null:
return
@ -267,10 +287,16 @@ func _capture_viewport_screenshot_async() -> void:
if err != OK:
push_warning("[AI ARENA] screenshot save failed: %s" % error_string(err))
return
var diag_zoom: float = -1.0
var vwm_diag: Node = _world_map.get_node_or_null("ViewportWindowManager")
if vwm_diag != null and vwm_diag.has_method("get_background_camera"):
var diag_cam: Camera2D = vwm_diag.get_background_camera()
if diag_cam != null:
diag_zoom = diag_cam.zoom.x
print(
(
"[AI ARENA] screenshot saved: %s (%dx%d)"
% [path, image.get_width(), image.get_height()]
"[AI ARENA] screenshot saved: %s (%dx%d) cam_zoom=%.3f"
% [path, image.get_width(), image.get_height(), diag_zoom]
)
)