diff --git a/src/game/engine/scenes/world_map/world_map_arena.gd b/src/game/engine/scenes/world_map/world_map_arena.gd index a298cc36..9d1e77c3 100644 --- a/src/game/engine/scenes/world_map/world_map_arena.gd +++ b/src/game/engine/scenes/world_map/world_map_arena.gd @@ -110,7 +110,10 @@ func _disable_blocking_overlays() -> void: func _apply_arena_camera_fit() -> void: ## Find the background camera and zoom out so the whole map fits the ## arena cell viewport. Called deferred so it runs after world_map's - ## own _start_game completes its camera setup. + ## own _start_game completes its camera setup. Also forces the map + ## SubViewport's update_mode to UPDATE_ALWAYS so Godot doesn't skip + ## its draw pass when the arena window is occluded by another arena + ## window — without this the offscreen matches show stale textures. var game_map: RefCounted = GameState.get_game_map() if game_map == null: return @@ -120,6 +123,13 @@ func _apply_arena_camera_fit() -> void: var cam: Camera2D = vwm.get_background_camera() if cam == null or not cam.has_method("fit_map_to_viewport"): return + + var bg_vp: SubViewport = null + if vwm.has_method("get_background_viewport"): + bg_vp = vwm.call("get_background_viewport") as SubViewport + if bg_vp != null: + bg_vp.render_target_update_mode = SubViewport.UPDATE_ALWAYS + var viewport: Viewport = _world_map.get_viewport() if viewport == null: return @@ -222,20 +232,25 @@ func _on_turn_ended(_turn_number: int, _player_index: int) -> void: func _capture_viewport_screenshot_async() -> void: - ## Godot on Wayland skips rendering occluded/unfocused windows, so a - ## raw `get_viewport().get_texture().get_image()` here grabs whatever - ## stale frame was last drawn — which for windows stacked behind - ## other arena instances is the loading-screen splash. Wait a few - ## idle frames AND force an explicit draw pass before grabbing, so - ## every window renders at least one real gameplay frame to its - ## texture before we read it back. + ## Capture the root viewport's texture after forcing a draw pass. + ## Godot on Wayland skips rendering occluded windows, so we set + ## update_mode = UPDATE_ALWAYS on the map SubViewport (once, when the + ## arena helper comes up) and, at capture time, yield a few frames + ## and call RenderingServer.force_draw() to flush the pipeline. The + ## world map renders into a SubViewport owned by ViewportWindowManager; + ## SubViewportContainer composites it into the root viewport each frame, + ## so grabbing the root viewport gives us HUD + map in one image. + ## (Grabbing the SubViewport directly returned 2x2 because its size is + ## tied to the Control's layout state, which isn't deterministic at + ## capture time.) if _result_dir.is_empty(): return var tree: SceneTree = _world_map.get_tree() - for _i: int in range(3): + for _i: int in range(4): await tree.process_frame RenderingServer.force_draw() await tree.process_frame + await tree.process_frame var viewport: Viewport = _world_map.get_viewport() if viewport == null: @@ -252,7 +267,12 @@ func _capture_viewport_screenshot_async() -> void: if err != OK: push_warning("[AI ARENA] screenshot save failed: %s" % error_string(err)) return - print("[AI ARENA] screenshot saved: %s" % path) + print( + ( + "[AI ARENA] screenshot saved: %s (%dx%d)" + % [path, image.get_width(), image.get_height()] + ) + ) func _on_victory(player_index: int, victory_type: String) -> void: